aws-cloudformation / cloudformation-coverage-roadmap

The AWS CloudFormation Public Coverage Roadmap
https://aws.amazon.com/cloudformation/
Creative Commons Attribution Share Alike 4.0 International
1.1k stars 53 forks source link

Allow ENIs to be specified when creating a VPC Endpoint #1254

Open andyRokit opened 1 year ago

andyRokit commented 1 year ago

Name of the resource

AWS::EC2::VPCEndpoint

Resource name

No response

Description

My use case As part of my stack I need to create a target group for a VpcE. To create the target group I need the IPs from the VpcE's ENIs. CloudFormation does not currently provide an option to specify the ENIs when creating a VpcE, instead they are provisioned automatically. This means that you do not have a handle on the IPs you need [1].

Feature request Add a new NetworkInterfaceIds attribute to AWS::EC2::VPCEndpoint. This could be used as an alternative to SubnetIds for creating the ENIs. This would allow manually defined ENIs to be used to create both the VpcE and the Target Group.

Workaround My current workaround involves creating the VpcE, then calling describe-network-interfaces [2] for each ENI via a custom resource. See CDK code in Other Details.

[1] https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#aws-resource-ec2-networkinterface-return-values

[2] https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ec2/describe-network-interfaces.html

Other Details

private createVpceTargetGroup(vpce: ec2.InterfaceVpcEndpoint): elbv2.NetworkTargetGroup {
    const targetGroup = new elbv2.NetworkTargetGroup(this, 'VpcE-Targets', {
        vpc: this.vpc,
        port: 443,
        targetGroupName: `${vpce.vpcEndpointId}-Targets`
    })

    for (let index = 0; index < this.vpc.availabilityZones.length; index++) {
        const getEndpointIp = new cr.AwsCustomResource(this, `GetEndpointIp${index}`, {
            onUpdate: {
                service: 'EC2',
                action: 'describeNetworkInterfaces',
                outputPaths: [`NetworkInterfaces.${index}.PrivateIpAddress`],
                parameters: { NetworkInterfaceIds: vpce.vpcEndpointNetworkInterfaceIds },
                physicalResourceId: cr.PhysicalResourceId.of(`NetworkInterfaces.${index}.PrivateIpAddress`)
            },
            policy: cr.AwsCustomResourcePolicy.fromSdkCalls({
                resources: cr.AwsCustomResourcePolicy.ANY_RESOURCE
            })
        });
        targetGroup.addTarget(new elbv2_targets.IpTarget(Token.asString(getEndpointIp.getResponseField(`NetworkInterfaces.${index}.PrivateIpAddress`))))
    }

    return targetGroup
}
ryanwilliams83 commented 10 months ago

I ended up with a slight variation on this to avoid the error validation failed for resource TargetGroup3D7CD9B8 with message: Targets: array items are not unique

const ipAddressProvider = new cr.AwsCustomResource(this, 'VpcEndpointIpAddressProvider', {
      // https://github.com/aws-cloudformation/cloudformation-coverage-roadmap/issues/1254
      onUpdate: {
          service: 'EC2',
          action: 'describeNetworkInterfaces',
          outputPaths: vpc.availabilityZones.map((_value, index) => `NetworkInterfaces.${index}.PrivateIpAddress`),
          parameters: {
            NetworkInterfaceIds: vpcEndpoint.vpcEndpointNetworkInterfaceIds
          },
          physicalResourceId: cr.PhysicalResourceId.of(Date.now().toString()),
      },
      policy: cr.AwsCustomResourcePolicy.fromSdkCalls({
          resources: cr.AwsCustomResourcePolicy.ANY_RESOURCE
      })
    });
    for (let index = 0; index < vpc.availabilityZones.length; index++)
      targetGroup.addTarget(new elbTargets.IpTarget(cdk.Token.asString(ipAddressProvider.getResponseField(`NetworkInterfaces.${index}.PrivateIpAddress`))));
jugarpeupv commented 1 month ago

hey @ryanwilliams83 could you expand on why your variation is needed to avoid the error? I am experiencing the error of Targets: array items are not unique but on random deployments and i do not understand why

ryanwilliams83 commented 1 month ago

@jugarpeupv, I suspect that the AWS API is returning the results in a different order when making multiple API calls.

e.g. First API Request CustomResource0 Index 0 = AZ-A = 192.168.1.1 Index 1 = AZ-B = 192.168.2.1 Index 2 = AZ-C = 192.168.3.1

Second API Request CustomResource1 Index 0 = AZ-B = 192.168.2.1 Index 1 = AZ-A = 192.168.1.1 Index 2 = AZ-C = 192.168.3.1

Third API Request CustomResource2 Index 0 = AZ-A = 192.168.1.1 Index 1 = AZ-B = 192.168.2.1 Index 2 = AZ-C = 192.168.3.1

So when the for loop executes it yeilds 192.168.1.1, 192.168.1.1, and 192.168.3.1 resulting in the error Targets: array items are not unique

My solution works reliably because it only contains a single occurrence of new cr.AwsCustomResource() which results in a single API call.

jugarpeupv commented 1 month ago

@ryanwilliams83 thanks for the explanation, it makes sense