-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(ec2): add support for vpc endpoints #2104
Conversation
Add support for both gateway and interface VPC endpoints. Static members are exposed for all AWS service endpoints. As gateway endpoints reference route tables, they currently cannot be added to imported VPC networks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be nice if @rix0rrr could also review this.
* A VPC endpoint service. | ||
*/ | ||
export class VpcEndpointService { | ||
constructor(public readonly name: string, public readonly type: VpcEndpointType) {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like it should be an interface?
* A VPC endpoint AWS service. | ||
*/ | ||
export class VpcEndpointAwsService extends VpcEndpointService { | ||
public static readonly SageMakeNotebook = new VpcEndpointAwsService('sagemaker', VpcEndpointType.Interface, 'aws.sagemaker'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SageMakerNotebook
└─ missing 'r'
public static readonly CodePipeline = new VpcEndpointAwsService('codepipeline'); | ||
public static readonly Config = new VpcEndpointAwsService('config'); | ||
public static readonly DynamoDb = new VpcEndpointAwsService('dynamodb', VpcEndpointType.Gateway); | ||
public static readonly Ec2 = new VpcEndpointAwsService('ec2'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Somehow I would rather name those EC2
, ECR
, SNS
, ... The PascalCased name hurts my eyes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree with you but when looking at other packages I find lots of PascalCased names:
https://github.com/awslabs/aws-cdk/blob/7001f7723cde611f0d8cd9c182f92120b09579c3/packages/%40aws-cdk/aws-sns/lib/subscription.ts#L89-L93
https://github.com/awslabs/aws-cdk/blob/7001f7723cde611f0d8cd9c182f92120b09579c3/packages/%40aws-cdk/aws-applicationautoscaling/lib/scalable-target.ts#L227-L246
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Your call
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm. I guess consistency is good. 🤷🏻♂️
Thanks for pointing those out 👍🏻
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pascal case is our standard. Otherwise jsii languages cannot delineate words when they convert cases.
} | ||
|
||
/** | ||
* A VPC endpoint AWS service. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would say A VPC endpoint for an AWS service
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An AWS VPC endpoint service? This is a service not an endpoint...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kinda think of those as "services that provide VPC endpoints"... But maybe my mental model is wrong here... This is actually why I was calling for @rix0rrr to have a look, too... I feel he might be a little more into this domain than I am.
* | ||
* @default private subnets | ||
*/ | ||
readonly subnets?: SubnetSelection[] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this really need to be an array?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For a VPC with isolated and private subnets, the user could want to add endpoint routing to the route tables of both subnet types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See the same kind of discussion here for VPN routing #1899 (comment).
If possible, I would like to refactor here to also use a SubnetSelection[]
:
https://github.com/awslabs/aws-cdk/blob/7001f7723cde611f0d8cd9c182f92120b09579c3/packages/%40aws-cdk/aws-ec2/lib/vpc.ts#L141-L147
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if it wouldn't be a better approach to make the SubnetSelection able to represent "private + isolated", don't you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean changing the SubnetSelection
interface to:
export interface SubnetSelection {
readonly subnetType?: SubnetType[];
readonly subnetName?: string[];
}
We also have to support a list of subnetName
s if the user created the VpcNetwork
with a specific subnetConfiguration
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 on extending SubnetSelection. That's our go-to
/** | ||
* A VPC endpoint service. | ||
*/ | ||
export interface VpcEndpointService { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs to be an IVpcEndpointService
(if something implements it begins with an "I").
public static readonly CodePipeline = new VpcEndpointAwsService('codepipeline'); | ||
public static readonly Config = new VpcEndpointAwsService('config'); | ||
public static readonly DynamoDb = new VpcEndpointAwsService('dynamodb', VpcEndpointType.Gateway); | ||
public static readonly Ec2 = new VpcEndpointAwsService('ec2'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pascal case is our standard. Otherwise jsii languages cannot delineate words when they convert cases.
public readonly type: VpcEndpointType; | ||
|
||
constructor(name: string, type?: VpcEndpointType, prefix?: string) { | ||
this.name = `${prefix || 'com.amazonaws'}.${cdk.Aws.region}.${name}`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to use some URL suffix token that's partition-aware (it's @RomainMuller's turf...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is AWS::URLSuffix
but reversed... don't know how does this look like for China regions...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@RomainMuller our resident expert on the topic, thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's probably going to be different yet again for noncommercial regions (one of those things that looks like a domain name but really isn't). I think this needs to be in RegionInfo.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well all the documentation says is that those are formatted as com.amazonaws.<region>.<service>
... And I couldn't find a reference table with more concrete information. So at this point I'd be willing to assume those are always formatted like so until proven wrong...
* | ||
* @default private subnets | ||
*/ | ||
readonly subnets?: SubnetSelection[] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 on extending SubnetSelection. That's our go-to
@@ -266,7 +287,7 @@ export abstract class VpcNetworkBase extends Construct implements IVpcNetwork { | |||
/** | |||
* Return the subnets appropriate for the placement strategy | |||
*/ | |||
protected subnets(selection: SubnetSelection = {}): IVpcSubnet[] { | |||
public subnets(selection: SubnetSelection = {}): IVpcSubnet[] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we rename this to selectSubnets
(to reflect SubnetSelection
)
const app = new cdk.App(); | ||
const stack = new cdk.Stack(app, 'aws-cdk-ec2-vpc-endpoint'); | ||
|
||
/// !show |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we put this in a subclass of stack so that first argument is always this
instead of stack
(which is non-idiomatic). I know we have many examples like that, but we should start somewhere...
} | ||
}); | ||
|
||
const dynamoDbEndpoint = vpc.addGatewayEndpoint('DynamoDbEndpoint', { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
indicate that this is just an alternative API
service: ec2.VpcEndpointAwsService.EcrDocker | ||
}); | ||
|
||
ecrDockerEndpoint.connections.allowFromAnyIPv4(new ec2.TcpPort(443)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
explain why this is needed
packages/@aws-cdk/aws-ec2/README.md
Outdated
@@ -353,3 +353,8 @@ const vpnConnection = vpc.addVpnConnection('Dynamic', { | |||
}); | |||
const state = vpnConnection.metricTunnelState(); | |||
``` | |||
|
|||
### VPC endpoints | |||
VPC gateway and interface endpoints can be added to a VPC: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copy & paste some official docs that explain what VPC endpoints are and why would I want to use them
BREAKING CHANGE: `SubnetSelection` now expects either a list of subnet types (`subnetTypes`) or a list of subnet names (`subnetNames`) BREAKING CHANGE: `vpnRoutePropagation` now expects a `SubnetSelection` BREAKING CHANGE: `vpcSubnets` in `ClusterProps` in `@aws-cdk/aws-eks` now expects a `SubnetSelection`
Just discovered that some interface endpoints also have a policy document (e.g. CodeCommit), this needs further investigation because the CF documentation is not clear (https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpcendpoint.html#cfn-ec2-vpcendpoint-policydocument) |
Strange... this build (https://travis-ci.org/awslabs/aws-cdk/builds/514128209#L1573) should be failing during the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me!
@PaulMaddox @leepa any chance you guys can take a quick look?
}); | ||
|
||
this.vpcEndpointId = endpoint.vpcEndpointId; | ||
this.creationTimestamp = endpoint.vpcEndpointCreationTimestamp; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use the vpcEndpoint prefix. It hints that this is a deploy-time attribute (soon more formally)
@@ -71,6 +72,11 @@ export interface IVpcNetwork extends IConstruct { | |||
*/ | |||
subnetIds(selection?: SubnetSelection): string[]; | |||
|
|||
/** | |||
* Return the subnets appropriate for the placement strategy |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we deprecate subnetIds? seems like those can be mapped from the subnet objects?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we still need it let’s rename to selectSubnetIds but I would just deprecate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean adding @deprecated
in JSDoc and removing all calls to this method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yap
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nooo don't!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've put effort into precisely into deprecating access to subnet objects and just getting IDs instead, because that is basically all the class consumers ever need from this class anyway. The previous interface was wider than anyone needed, and uncomfortable for imported VPCs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you elaborate why returning IDs is a better API? It is in contradiction with one of our design tents to use strong types instead of IDs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First of all: convenience. A subnet is never what a consumer needs. It always needs a list of subnet IDs, to pass to CloudFormation. Why would you make a consumer grub around in the innards of my data structures, to do exactly the same operation in all places where it's called? I might as well do that for you.
Second of all: it allows us more flexilibity around exports and imports. The function:
function subnetIds(): string[];
Can be satisfied by a { Fn::ImportValue: Export }
or a { Ref: Parameter }
. The function:
function subnets(): ISubnet[]
Can not. Because of this, it's super convoluted to export/import VPCs across stacks today. It could all be dramatically simplified if we hadn't painted ourselves into a corner with an API that promises too much, for no good reason because all consumers do is map across those subnets to get the IDs anyway!
And if we're invoking design principles: it violates the Law of Demeter. The VPC is your abstraction, and you shouldn't depend on how it's implemented.
Sorry, I apparently should have paid attention to this PR earlier. Why do we need access to subnet objects? I just spent #2025 limiting access to it. |
Also, I don't agree with the change of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apart from the VPC endpoint support, I don't like the API-complicating changes that are being introduced here to VpcNetwork. Please tell me why they are necessary.
@rix0rrr here are a few examples where selecting multiple subnet types are needed:
My first proposal was to simply use a I let you guys decide on this. PS: it's pretty easy to revert |
We need to specify the route table ids in AWS::EC2::VPCEndpoint (https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpcendpoint.html#cfn-ec2-vpcendpoint-routetableids). Do you suggest adding |
No, I would suggest adding an |
How were you thinking about this? |
By the way, something else I would also be very okay with if we want to model the result of subnet selection as an object, is something like this: const selection = vpc.selectSubnets({ subnetType: Public });
const ids = selection.subnetIds;
const azs = selection.availabilityZones;
// And even
selection.addRoute(...);
selection.addGateway(...);
selection.addInterface(...); |
I was thinking about improving this: same use case here: |
I think these are not created equal. In the first case, I feel like the comment is a little misleading.
What I'm saying is, the comment makes things sound more dire than they actually are. If we wanted this feature, the right place to enforce this functionality would be at the VPC level: // Make VpcNetwork guarantee this property if it can
const ids = vpc.selectSubnetIds({ ...props.subnets, atLeast2UniqueAZs: true }); But this only becomes relevant if subnet selection becomes more expressive than it is today. Today 1 subnet would map to 1 AZ, and at least 2 of one maps to at least 2 of the other. For the VPC Endpoint case, the story is similar. Either the property you're looking for (exactly one subnet per AZ... though maybe the actual constraint is at most one subnet per AZ?) is either guaranteed by construction, or is untestable anyway. |
This is not always guaranteed by construction, see the following test case I wrote https://github.com/awslabs/aws-cdk/blob/fd1729977c969a6b22c0b5ffe66d8b71115b1501/packages/%40aws-cdk/aws-ec2/test/test.vpc-endpoint.ts#L297 From the console experience, I think it is one subnet per AZ (if I specify 2 subnets then I need those to be in 2 different AZs,
This method has been implemented at the In order to go forward with this PR, could you have a look at the latest changes from today and tell me what you would like to be adapted? |
Oh, you are right of course, for some reason I was thinking about selecting subnets by name and missed this case. In any case, I still think the solution is to put the selection logic inside
Well that is perfect. I only went down this discussion because of the following exchange:
Your response made me assume we needed the route table IDs outside the VPC construct. If route table IDs are an implementation detail used inside the VPC to implement the functions you mentioned before, we don't have a problem at all.
Will do. |
Explicitly deal with the fact that not all IVpcSubnets are VpcSubnets.
How about the change that I just pushed? I think it's an okay compromise. All I ask is that we treat the subnets as one group of things, to keep the API surface small and not rely too much on implementation details. It does deprecate Also added dealing with the case where the VpcSubnets would be imported, which wasn't deal with before as far as I could see, all |
Right now I did the |
@rix0rrr added a fix to Do you want to leave the calls to |
If you're volunteering to update them, not at all :) |
Also thanks for fixing my hasty updates |
Will update, this also means that |
Add support for both gateway and interface VPC endpoints. Static members are
exposed for all AWS service endpoints.
As gateway endpoints reference route tables, they currently cannot be added to
imported VPC networks.
BREAKING CHANGE:
subnetIds
is now replaced byselectSubnets
which returns an object containingsubnetIds
.BREAKING CHANGE:
vpnRoutePropagation
now expects aSubnetSelection[]
Pull Request Checklist
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license.