Skip to content
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): VpcEndpointService construct #5498

Merged
merged 24 commits into from
Jan 7, 2020
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
09914ba
feat(ec2): add vpc endpoint service construct
Dec 18, 2019
58cf9de
Need to add the CDK suffixes to the expected
flemjame-at-amazon Dec 19, 2019
48672d9
Use the CDK magic strings
flemjame-at-amazon Dec 19, 2019
118ca7f
Changing to ArnPrincipal instead of string
flemjame-at-amazon Dec 19, 2019
056c86c
Merge branch 'master' into add-vpc-endpoint-service
flemjame-at-amazon Dec 20, 2019
aab1ee6
Review feedback
flemjame-at-amazon Dec 20, 2019
406b480
Merge branch 'master' into add-vpc-endpoint-service
flemjame-at-amazon Dec 20, 2019
fe8b2e0
Merge branch 'master' into add-vpc-endpoint-service
flemjame-at-amazon Dec 23, 2019
73ee41f
Merge branch 'master' into add-vpc-endpoint-service
flemjame-at-amazon Dec 30, 2019
7d0878d
Removing tests and adding IVpcEndpointService
flemjame-at-amazon Dec 30, 2019
f69411d
NLB inherits IVpcEndpointServiceLoadBalancer
flemjame-at-amazon Dec 31, 2019
c2983ec
Update tests
flemjame-at-amazon Dec 31, 2019
edc37ba
Add integration tests back
flemjame-at-amazon Dec 31, 2019
0ab1d09
Update readme
flemjame-at-amazon Dec 31, 2019
e388f79
Small readme addition
flemjame-at-amazon Dec 31, 2019
1e03f25
Merge branch 'master' into add-vpc-endpoint-service
flemjame-at-amazon Dec 31, 2019
a92196f
Merge branch 'master' into add-vpc-endpoint-service
flemjame-at-amazon Jan 6, 2020
0ee8de4
Review feedback
flemjame-at-amazon Jan 6, 2020
a776e7e
Not sure why that changed
flemjame-at-amazon Jan 6, 2020
01a1b0e
Mark as experimental
flemjame-at-amazon Jan 6, 2020
5601ae6
Oops, shouldn't have marked that as experimental
flemjame-at-amazon Jan 6, 2020
ecaa6d1
Anothr thing to not mark as experimental
flemjame-at-amazon Jan 6, 2020
bf801a8
Merge branch 'master' into add-vpc-endpoint-service
mergify[bot] Jan 7, 2020
434b47b
Merge branch 'master' into add-vpc-endpoint-service
mergify[bot] Jan 7, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions packages/@aws-cdk/aws-ec2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,16 @@ myEndpoint.connections.allowDefaultPortFromAnyIpv4();

Alternatively, existing security groups can be used by specifying the `securityGroups` prop.

## VPC endpoint services
A VPC endpoint service enables you to expose a Network Load Balancer(s) as a provider service to consumers, who connect to your service over a VPC endpoint. You can restrict access to your service via whitelisted principals (anything that extends ArnPrincipal), and require that new connections be manually accepted.
```ts
new VpcEndpointService(this, "EndpointService", {
vpcEndpointServiceLoadBalancers: [networkLoadBalancer1, networkLoadBalancer2],
acceptanceRequired: true,
whitelistedPrincipals: [new ArnPrincipal("arn:aws:iam::123456789012:root")]
});
```

## Bastion Hosts
A bastion host functions as an instance used to access servers and resources in a VPC without open up the complete VPC on a network level.
You can use bastion hosts using a standard SSH connection targetting port 22 on the host. As an alternative, you can connect the SSH connection
Expand Down
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-ec2/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export * from './vpc';
export * from './vpc-lookup';
export * from './vpn';
export * from './vpc-endpoint';
export * from './vpc-endpoint-service';
export * from './user-data';
export * from './windows-versions';

Expand Down
115 changes: 115 additions & 0 deletions packages/@aws-cdk/aws-ec2/lib/vpc-endpoint-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { ArnPrincipal } from '@aws-cdk/aws-iam';
import { Construct, IResource, Resource } from '@aws-cdk/core';
import { CfnVPCEndpointService, CfnVPCEndpointServicePermissions } from './ec2.generated';

/**
* A load balancer that can host a VPC Endpoint Service
*
*/
export interface IVpcEndpointServiceLoadBalancer {
/**
* The ARN of the load balancer that hosts the VPC Endpoint Service
*/
readonly loadBalancerArn: string;
}

/**
* A VPC endpoint service.
* @experimental
*/
export interface IVpcEndpointService extends IResource {
flemjame-at-amazon marked this conversation as resolved.
Show resolved Hide resolved
/**
* Name of the Vpc Endpoint Service
* @experimental
*/
readonly vpcEndpointServiceName?: string;
}

/**
* A VPC endpoint service
* @resource AWS::EC2::VPCEndpointService
* @experimental
*/
export class VpcEndpointService extends Resource implements IVpcEndpointService {

/**
* One or more network load balancer ARNs to host the service.
* @attribute
*/
public readonly vpcEndpointServiceLoadBalancers: IVpcEndpointServiceLoadBalancer[];

/**
* Whether to require manual acceptance of new connections to the service.
* @experimental
*/
public readonly acceptanceRequired: boolean;

/**
* One or more Principal ARNs to allow inbound connections to.
* @experimental
*/
public readonly whitelistedPrincipals: ArnPrincipal[];

private readonly endpointService: CfnVPCEndpointService;

constructor(scope: Construct, id: string, props: VpcEndpointServiceProps) {
super(scope, id);

if (props.vpcEndpointServiceLoadBalancers === undefined || props.vpcEndpointServiceLoadBalancers.length === 0) {
throw new Error("VPC Endpoint Service must have at least one load balancer specified.");
}

this.vpcEndpointServiceLoadBalancers = props.vpcEndpointServiceLoadBalancers;
this.acceptanceRequired = props.acceptanceRequired !== undefined ? props.acceptanceRequired : true;
this.whitelistedPrincipals = props.whitelistedPrincipals !== undefined ? props.whitelistedPrincipals : [];

this.endpointService = new CfnVPCEndpointService(this, id, {
networkLoadBalancerArns: this.vpcEndpointServiceLoadBalancers.map(lb => lb.loadBalancerArn),
acceptanceRequired: this.acceptanceRequired
});

if (this.whitelistedPrincipals.length > 0) {
new CfnVPCEndpointServicePermissions(this, "Permissions", {
serviceId: this.endpointService.ref,
allowedPrincipals: this.whitelistedPrincipals.map(x => x.arn)
});
}
}
}

/**
* Construction properties for a VpcEndpointService.
* @experimental
*/
export interface VpcEndpointServiceProps {

/**
* Name of the Vpc Endpoint Service
* @default - CDK generated name
* @experimental
*/
readonly vpcEndpointServiceName?: string;

/**
* One or more load balancers to host the VPC Endpoint Service.
* @experimental
*/
readonly vpcEndpointServiceLoadBalancers: IVpcEndpointServiceLoadBalancer[];

/**
* Whether requests from service consumers to connect to the service through
* an endpoint must be accepted.
* @default true
* @experimental
*/
readonly acceptanceRequired?: boolean;

/**
* IAM users, IAM roles, or AWS accounts to allow inbound connections from.
* These principals can connect to your service using VPC endpoints. Takes a
* list of one or more ArnPrincipal.
* @default - no principals
* @experimental
*/
readonly whitelistedPrincipals?: ArnPrincipal[];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"Resources": {
"MyVpcEndpointServiceWithNoPrincipals9B24276E": {
"Type": "AWS::EC2::VPCEndpointService",
"Properties": {
"NetworkLoadBalancerArns": [
"arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/net/Test/9bn6qkf4e9jrw77a"
],
"AcceptanceRequired": false
}
},
"MyVpcEndpointServiceWithPrincipals41EE2DF2": {
"Type": "AWS::EC2::VPCEndpointService",
"Properties": {
"NetworkLoadBalancerArns": [
"arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/net/Test/1jd81k39sa421ffs"
],
"AcceptanceRequired": false
}
},
"MyVpcEndpointServiceWithPrincipalsPermissions29F9BD5A": {
"Type": "AWS::EC2::VPCEndpointServicePermissions",
"Properties": {
"ServiceId": {
"Ref": "MyVpcEndpointServiceWithPrincipals41EE2DF2"
},
"AllowedPrincipals": [
"arn:aws:iam::123456789012:root"
]
}
}
}
}
46 changes: 46 additions & 0 deletions packages/@aws-cdk/aws-ec2/test/integ.vpc-endpoint-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { ArnPrincipal } from '@aws-cdk/aws-iam';
import * as cdk from '@aws-cdk/core';
import * as ec2 from '../lib';

const app = new cdk.App();

/**
* A load balancer that can host a VPC Endpoint Service
*/
class DummyEndpointLoadBalacer implements ec2.IVpcEndpointServiceLoadBalancer {
/**
* The ARN of the load balancer that hosts the VPC Endpoint Service
*/
public readonly loadBalancerArn: string;
constructor(arn: string) {
this.loadBalancerArn = arn;
}
}

class VpcEndpointServiceStack extends cdk.Stack {
constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
super(scope, id, props);

const nlbNoPrincipals = new DummyEndpointLoadBalacer(
"arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/net/Test/9bn6qkf4e9jrw77a");

new ec2.VpcEndpointService(this, "MyVpcEndpointServiceWithNoPrincipals", {
vpcEndpointServiceLoadBalancers: [nlbNoPrincipals],
acceptanceRequired: false,
whitelistedPrincipals: []
});

const nlbWithPrincipals = new DummyEndpointLoadBalacer(
"arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/net/Test/1jd81k39sa421ffs");
const principalArn = new ArnPrincipal("arn:aws:iam::123456789012:root");

new ec2.VpcEndpointService(this, "MyVpcEndpointServiceWithPrincipals", {
vpcEndpointServiceLoadBalancers: [nlbWithPrincipals],
acceptanceRequired: false,
whitelistedPrincipals: [principalArn]
});
}
}

new VpcEndpointServiceStack(app, 'aws-cdk-ec2-vpc-endpoint-service');
app.synth();
106 changes: 106 additions & 0 deletions packages/@aws-cdk/aws-ec2/test/test.vpc-endpoint-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { expect, haveResource } from '@aws-cdk/assert';
import { ArnPrincipal } from '@aws-cdk/aws-iam';
import { Stack } from '@aws-cdk/core';
import { Test } from 'nodeunit';
// tslint:disable-next-line:max-line-length
import { IVpcEndpointServiceLoadBalancer, Vpc, VpcEndpointService } from '../lib';

/**
* A load balancer that can host a VPC Endpoint Service
*/
class DummyEndpointLoadBalacer implements IVpcEndpointServiceLoadBalancer {
/**
* The ARN of the load balancer that hosts the VPC Endpoint Service
*/
public readonly loadBalancerArn: string;
constructor(arn: string) {
this.loadBalancerArn = arn;
}
}

export = {
'test vpc endpoint service': {
'create endpoint service with no principals'(test: Test) {
// GIVEN
const stack = new Stack();
new Vpc(stack, "MyVPC");

// WHEN
const lb = new DummyEndpointLoadBalacer("arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/net/Test/9bn6qkf4e9jrw77a");
new VpcEndpointService(stack, "EndpointService", {
vpcEndpointServiceLoadBalancers: [lb],
acceptanceRequired: false,
whitelistedPrincipals: [new ArnPrincipal("arn:aws:iam::123456789012:root")]
});
// THEN
expect(stack).to(haveResource('AWS::EC2::VPCEndpointService', {
NetworkLoadBalancerArns: ["arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/net/Test/9bn6qkf4e9jrw77a"],
AcceptanceRequired: false
}));

expect(stack).notTo(haveResource('AWS::EC2::VPCEndpointServicePermissions', {
ServiceId: {
Ref: "EndpointServiceED36BE1F"
},
AllowedPrincipals: []
}));

test.done();
},
'create endpoint service with a principal'(test: Test) {
// GIVEN
const stack = new Stack();

// WHEN
const lb = new DummyEndpointLoadBalacer("arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/net/Test/9bn6qkf4e9jrw77a");
new VpcEndpointService(stack, "EndpointService", {
vpcEndpointServiceLoadBalancers: [lb],
acceptanceRequired: false,
whitelistedPrincipals: [new ArnPrincipal("arn:aws:iam::123456789012:root")]
});

// THEN
expect(stack).to(haveResource('AWS::EC2::VPCEndpointService', {
NetworkLoadBalancerArns: ["arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/net/Test/9bn6qkf4e9jrw77a"],
AcceptanceRequired: false
}));

expect(stack).to(haveResource('AWS::EC2::VPCEndpointServicePermissions', {
ServiceId: {
Ref: "EndpointServiceED36BE1F"
flemjame-at-amazon marked this conversation as resolved.
Show resolved Hide resolved
},
AllowedPrincipals: ["arn:aws:iam::123456789012:root"]
}));

test.done();
},

'with acceptance requried'(test: Test) {
// GIVEN
const stack = new Stack();

// WHEN
const lb = new DummyEndpointLoadBalacer("arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/net/Test/9bn6qkf4e9jrw77a");
new VpcEndpointService(stack, "EndpointService", {
vpcEndpointServiceLoadBalancers: [lb],
acceptanceRequired: true,
whitelistedPrincipals: [new ArnPrincipal("arn:aws:iam::123456789012:root")]
});

// THEN
expect(stack).to(haveResource('AWS::EC2::VPCEndpointService', {
NetworkLoadBalancerArns: ["arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/net/Test/9bn6qkf4e9jrw77a"],
AcceptanceRequired: true
}));

expect(stack).to(haveResource('AWS::EC2::VPCEndpointServicePermissions', {
ServiceId: {
Ref: "EndpointServiceED36BE1F"
},
AllowedPrincipals: ["arn:aws:iam::123456789012:root"]
}));

test.done();
}
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -227,11 +227,7 @@ export class NetworkLoadBalancer extends BaseLoadBalancer implements INetworkLoa
/**
* A network load balancer
*/
export interface INetworkLoadBalancer extends ILoadBalancerV2 {
/**
* The ARN of this load balancer
*/
readonly loadBalancerArn: string;
export interface INetworkLoadBalancer extends ILoadBalancerV2, ec2.IVpcEndpointServiceLoadBalancer {

/**
* The VPC this load balancer has been created in (if available)
Expand Down