skip to Main Content

I have a VPC endpoint, and I want to retrieve its private IP addresses (to map to an Application Load Balancer through a Target Group).

I do that in two steps:

  1. First I create an AwsCustomResource to retrieve its Network Interface IDs, using EC2::describeVpcEndpoints
  2. Then I create a second CustomResource to retrieve the private IP addresses, using EC2::describeNetworkInterfaces (with the previous Network Interface IDs passed in parameters)

Here my CDK code:

// 1st get the VPC endpoint network interface IDs
const vpcEndpointsCall: AwsSdkCall = {
    service: "EC2",
    action: "describeVpcEndpoints",
    outputPaths: [`VpcEndpoints.0.NetworkInterfaceIds`],
    parameters: {
        VpcEndpointIds: [myVpcEndpointId],
    },
    physicalResourceId: PhysicalResourceId.of('describeVpcEndpoints'),
}

const vpces = new AwsCustomResource(
    this.stack,
    `DescribeVpcEndpoints-${this.stackProps.deploy.environmentName}`,
    {
        onCreate: vpcEndpointsCall,
        onUpdate: vpcEndpointsCall,
        policy: {
            statements: [
                new PolicyStatement({
                    actions: ["ec2:DescribeVpcEndpoints"],
                    resources: ["*"],
                }),
            ],
        },
    }
);
        
const networkInterfaceIds = vpces.getResponseField(`VpcEndpoints.0.NetworkInterfaceIds`)

for (let index = 0; index < this.stackProps.core!.vpc.availabilityZones.length; index++) {
    
    // 2nd get the private IP addresses of the VPC endpoint network interface IDs
    const sdkCall: AwsSdkCall = {
        service: "EC2",
        action: "describeNetworkInterfaces",
        outputPaths: [`NetworkInterfaces.${index}.PrivateIpAddress`],
        parameters: {
            NetworkInterfaceIds: Token.asString(networkInterfaceIds),
            Filters: [
                { Name: "interface-type", Values: ["vpc_endpoint"] }
            ],
        },
        physicalResourceId: PhysicalResourceId.of('describeNetworkInterfaces'),
    }

    const eni = new AwsCustomResource(
        this.stack,
        `DescribeNetworkInterfaces-${this.stackProps.deploy.environmentName}${index}`,
        {
            onCreate: sdkCall,
            onUpdate: sdkCall,
            policy: {
                statements: [
                    new PolicyStatement({
                        actions: ["ec2:DescribeNetworkInterfaces"],
                        resources: ["*"],
                    }),
                ],
            },
        }
    );

    targetGroup.addTarget(new IpTarget(Token.asString(eni.getResponseField(`NetworkInterfaces.${index}.PrivateIpAddress`)), targetPort))
}

But during the cdk deploy, the following error is raised:

CustomResource attribute error: Vendor response doesn't contain VpcEndpoints.0.NetworkInterfaceIds key in object arn:aws:cloudformation:eu-west-3:XXXXXXXXXX:stack/CDK-Core-EC2-XXXXXXXXXX/XXXXXXXXXX|DescribeVpcEndpointsXXXXXXXXXX|XXXXXXXXXX in S3 bucket cloudformation-custom-resource-storage-euwest3

Here is the result of the DescribeVpcEndpoints call:

{
    "VpcEndpoints": [
        {
            "VpcEndpointId": "XXX",
            "VpcEndpointType": "Interface",
            "VpcId": "XXX",
            "ServiceName": "com.amazonaws.eu-west-3.execute-api",
            "State": "available",
            "PolicyDocument": "{n  "Statement": [n    {n      "Action": "*", n      "Effect": "Allow", n      "Principal": "*", n      "Resource": "*"n    }n  ]n}",       
            "RouteTableIds": [],
            "SubnetIds": [
                "subnet-XXX",
                "subnet-XXX",
                "subnet-XXX"
            ],
            "Groups": [
                {
                    "GroupId": "XXX",
                    "GroupName": "XXX"
                },
                {
                    "GroupId": "XXX",
                    "GroupName": "XXX"
                }
            ],
            "IpAddressType": "ipv4",
            "DnsOptions": {
                "DnsRecordIpType": "ipv4"
            },
            "PrivateDnsEnabled": true,
            "RequesterManaged": false,
            "NetworkInterfaceIds": [
                "eni-XXX",
                "eni-XXX",
                "eni-XXX"
            ],
            "DnsEntries": [
                {
                    "DnsName": "vpce-XXX.execute-api.eu-west-3.vpce.amazonaws.com",
                    "HostedZoneId": "XXX"
                },
                {
                    "DnsName": "vpce-XXX-eu-west-3c.execute-api.eu-west-3.vpce.amazonaws.com",
                    "HostedZoneId": "XXX"
                },
                {
                    "DnsName": "vpce-XXX-eu-west-3b.execute-api.eu-west-3.vpce.amazonaws.com",
                    "HostedZoneId": "XXX"
                },
                {
                    "DnsName": "vpce-XXX-eu-west-3a.execute-api.eu-west-3.vpce.amazonaws.com",
                    "HostedZoneId": "XXX"
                },
                {
                    "DnsName": "execute-api.eu-west-3.amazonaws.com",
                    "HostedZoneId": "XXX"
                },
                {
                    "DnsName": "*.execute-api.eu-west-3.amazonaws.com",
                    "HostedZoneId": "XXX"
                }
            ],
            "CreationTimestamp": "XXX",
            "Tags": [],
            "OwnerId": "XXX"
        }
}

So I don’t understand why the getResponseField('VpcEndpoints.0.NetworkInterfaceIds') is not working.

I expect to have the list of the Network Interface IDs in the field VpcEndpoints.0.NetworkInterfaceIds.

Any idea?

2

Answers


  1. Chosen as BEST ANSWER

    I answer myself because I found something interesting in the Custom Resource (Lambda) logs:

    {
        "Status": "SUCCESS",
        "Reason": "OK",
        "PhysicalResourceId": "1692795531828",
        "StackId": "arn:aws:cloudformation:us-east-1:...:stack/XXX/XXX",
        "RequestId": "XXX",
        "LogicalResourceId": "DescribeVpcEndpoints16CD45EC",
        "NoEcho": false,
        "Data": {. <---- Values to be referenced by “vpces.getResponseField()” method
            "VpcEndpoints.0.NetworkInterfaceIds.0": "eni-...",
            "VpcEndpoints.0.NetworkInterfaceIds.1": "eni-…",
            "VpcEndpoints.0.NetworkInterfaceIds.2": "eni-…",
            "VpcEndpoints.0.NetworkInterfaceIds.3": "eni-…",
            "VpcEndpoints.0.NetworkInterfaceIds.4": "eni-…"
        }
    }
    

    So I need to manage each Network Interface individually.

    The fixed code is now:

    // 1st get the VPC endpoint network interface IDs
    const vpcEndpointsCall: AwsSdkCall = {
        service: "EC2",
        action: "describeVpcEndpoints",
        outputPaths: [`VpcEndpoints.0.NetworkInterfaceIds`],
        parameters: {
            VpcEndpointIds: [vpceId],
        },
        physicalResourceId: PhysicalResourceId.of('describeVpcEndpoints'),
    }
    
    const vpces = new AwsCustomResource(
        this.stack,
        `DescribeVpcEndpoints-vpceDns${vpcePrivateDnsStatus}-${this.stackProps.deploy.environmentName}`,
        {
            onCreate: vpcEndpointsCall,
            onUpdate: vpcEndpointsCall,
            policy: {
                statements: [
                    new PolicyStatement({
                        actions: ["ec2:DescribeVpcEndpoints"],
                        resources: ["*"],
                    }),
                ],
            },
        }
    );
    
    // const networkInterfaceIds = vpces.getResponseField(`VpcEndpoints.0.NetworkInterfaceIds`)
    // console.log("NetworkInterfaceIds: " + Token.asString(networkInterfaceIds))
    
    for (let index = 0; index < this.stackProps.core!.vpc.availabilityZones.length; index++) {
        
        // 2nd get the private IP addresses of the VPC endpoint network interface IDs
        const sdkCall: AwsSdkCall = {
            service: "EC2",
            action: "describeNetworkInterfaces",
            outputPaths: [`NetworkInterfaces.0.PrivateIpAddress`],
            parameters: {
                NetworkInterfaceIds: [vpces.getResponseField(`VpcEndpoints.0.NetworkInterfaceIds.${index}`)],
                Filters: [
                    { Name: "interface-type", Values: ["vpc_endpoint"] }
                ],
            },
            physicalResourceId: PhysicalResourceId.of('describeNetworkInterfaces'),
        }
    
        const eni = new AwsCustomResource(
            this.stack,
            `DescribeNetworkInterfaces-vpceDns${vpcePrivateDnsStatus}-${this.stackProps.deploy.environmentName}${index}`,
            {
                onCreate: sdkCall,
                onUpdate: sdkCall,
                policy: {
                    statements: [
                        new PolicyStatement({
                            actions: ["ec2:DescribeNetworkInterfaces"],
                            resources: ["*"],
                        }),
                    ],
                },
            }
        );
    
        targetGroup.addTarget(new IpTarget(Token.asString(eni.getResponseField(`NetworkInterfaces.0.PrivateIpAddress`)), targetPort))
    }
    

    It works perfectly now.


  2. Just fixed the same issue: your problem is Token.asString(networkInterfaceIds), Token.asString cannot handle Arrays.

    You have to access each array entry itself by index:

    Token.asString(networkInterfaceIds)[0]

    Token.asString(networkInterfaceIds)[1]

    Also you should use Token.asString everywhere, so you have to restructure your code a little bit.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search