Skip to content

Commit

Permalink
feat(ec2): allow using existing security groups with interface VPC en…
Browse files Browse the repository at this point in the history
…dpoints (aws#4908)

Support a `securityGroups` prop for interface VPC endpoints in a non breaking way.

Clarify documentation on interface VPC endpoints connections.

Add missing Storage Gateway.

Closes aws#4589
Closes aws#2699
Closes aws#3446
  • Loading branch information
jogold authored and mergify[bot] committed Nov 8, 2019
1 parent 0cd6780 commit bda28e8
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 15 deletions.
11 changes: 11 additions & 0 deletions packages/@aws-cdk/aws-ec2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,17 @@ Endpoints are virtual devices. They are horizontally scaled, redundant, and high

[example of setting up VPC endpoints](test/integ.vpc-endpoint.lit.ts)

### Security groups for interface VPC endpoints
By default, interface VPC endpoints create a new security group and traffic is **not**
automatically allowed from the VPC CIDR.

Use the `connections` object to allow traffic to flow to the endpoint:
```ts
myEndpoint.connections.allowDefaultPortFrom(...);
```

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

## 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
49 changes: 37 additions & 12 deletions packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Aws, Construct, IResource, Lazy, Resource } from '@aws-cdk/core';
import { Connections, IConnectable } from './connections';
import { CfnVPCEndpoint } from './ec2.generated';
import { Port } from './port';
import { SecurityGroup } from './security-group';
import { ISecurityGroup, SecurityGroup } from './security-group';
import { allRouteTableIds } from './util';
import { IVpc, SubnetSelection, SubnetType } from './vpc';

Expand Down Expand Up @@ -243,6 +243,7 @@ export class InterfaceVpcEndpointAwsService implements IInterfaceVpcEndpointServ
public static readonly SSM_MESSAGES = new InterfaceVpcEndpointAwsService('ssmmessages');
public static readonly STS = new InterfaceVpcEndpointAwsService('sts');
public static readonly TRANSFER = new InterfaceVpcEndpointAwsService('transfer.server');
public static readonly STORAGE_GATEWAY = new InterfaceVpcEndpointAwsService('storagegateway');

/**
* The name of the service.
Expand Down Expand Up @@ -281,9 +282,16 @@ export interface InterfaceVpcEndpointOptions {
* The subnets in which to create an endpoint network interface. At most one
* per availability zone.
*
* @default private subnets
* @default - private subnets
*/
readonly subnets?: SubnetSelection;

/**
* The security groups to associate with this interface VPC endpoint.
*
* @default - a new security group is created
*/
readonly securityGroups?: ISecurityGroup[];
}

/**
Expand Down Expand Up @@ -311,12 +319,19 @@ export class InterfaceVpcEndpoint extends VpcEndpoint implements IInterfaceVpcEn
* Imports an existing interface VPC endpoint.
*/
public static fromInterfaceVpcEndpointAttributes(scope: Construct, id: string, attrs: InterfaceVpcEndpointAttributes): IInterfaceVpcEndpoint {
if (!attrs.securityGroups && !attrs.securityGroupId) {
throw new Error('Either `securityGroups` or `securityGroupId` must be specified.');
}

const securityGroups = attrs.securityGroupId
? [SecurityGroup.fromSecurityGroupId(scope, 'SecurityGroup', attrs.securityGroupId)]
: attrs.securityGroups;

class Import extends Resource implements IInterfaceVpcEndpoint {
public readonly vpcEndpointId = attrs.vpcEndpointId;
public readonly securityGroupId = attrs.securityGroupId;
public readonly connections = new Connections({
defaultPort: Port.tcp(attrs.port),
securityGroups: [SecurityGroup.fromSecurityGroupId(this, 'SecurityGroup', attrs.securityGroupId)],
securityGroups,
});
}

Expand Down Expand Up @@ -347,8 +362,10 @@ export class InterfaceVpcEndpoint extends VpcEndpoint implements IInterfaceVpcEn
public readonly vpcEndpointNetworkInterfaceIds: string[];

/**
* The identifier of the security group associated with this interface VPC
* endpoint.
* The identifier of the first security group associated with this interface
* VPC endpoint.
*
* @deprecated use the `connections` object
*/
public readonly securityGroupId: string;

Expand All @@ -360,13 +377,13 @@ export class InterfaceVpcEndpoint extends VpcEndpoint implements IInterfaceVpcEn
constructor(scope: Construct, id: string, props: InterfaceVpcEndpointProps) {
super(scope, id);

const securityGroup = new SecurityGroup(this, 'SecurityGroup', {
const securityGroups = props.securityGroups || [new SecurityGroup(this, 'SecurityGroup', {
vpc: props.vpc
});
this.securityGroupId = securityGroup.securityGroupId;
})];
this.securityGroupId = securityGroups[0].securityGroupId;
this.connections = new Connections({
defaultPort: Port.tcp(props.service.port),
securityGroups: [securityGroup]
securityGroups
});

const subnets = props.vpc.selectSubnets({ ...props.subnets, onePerAz: true });
Expand All @@ -375,7 +392,7 @@ export class InterfaceVpcEndpoint extends VpcEndpoint implements IInterfaceVpcEn
const endpoint = new CfnVPCEndpoint(this, 'Resource', {
privateDnsEnabled: props.privateDnsEnabled !== undefined ? props.privateDnsEnabled : true,
policyDocument: Lazy.anyValue({ produce: () => this.policyDocument }),
securityGroupIds: [this.securityGroupId],
securityGroupIds: securityGroups.map(s => s.securityGroupId),
serviceName: props.service.name,
vpcEndpointType: VpcEndpointType.INTERFACE,
subnetIds,
Expand All @@ -400,8 +417,16 @@ export interface InterfaceVpcEndpointAttributes {

/**
* The identifier of the security group associated with the interface VPC endpoint.
*
* @deprecated use `securityGroups` instead
*/
readonly securityGroupId?: string;

/**
* The security groups associated with the interface VPC endpoint.
*
*/
readonly securityGroupId: string;
readonly securityGroups?: ISecurityGroup[];

/**
* The port of the service of the interface VPC endpoint.
Expand Down
5 changes: 4 additions & 1 deletion packages/@aws-cdk/aws-ec2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@
"docs-public-apis:@aws-cdk/aws-ec2.InterfaceVpcEndpointAwsService.SSM_MESSAGES",
"docs-public-apis:@aws-cdk/aws-ec2.InterfaceVpcEndpointAwsService.STS",
"docs-public-apis:@aws-cdk/aws-ec2.InterfaceVpcEndpointAwsService.TRANSFER",
"docs-public-apis:@aws-cdk/aws-ec2.InterfaceVpcEndpointAwsService.STORAGE_GATEWAY",
"docs-public-apis:@aws-cdk/aws-ec2.Port.toString",
"docs-public-apis:@aws-cdk/aws-ec2.PrivateSubnet.fromPrivateSubnetAttributes",
"docs-public-apis:@aws-cdk/aws-ec2.PublicSubnet.fromPublicSubnetAttributes",
Expand Down Expand Up @@ -457,7 +458,9 @@
"docs-public-apis:@aws-cdk/aws-ec2.WindowsVersion.WINDOWS_SERVER_2016_CHINESE_SIMPLIFIED_FULL_BASE",
"docs-public-apis:@aws-cdk/aws-ec2.WindowsVersion.WINDOWS_SERVER_2019_POLISH_FULL_BASE",
"docs-public-apis:@aws-cdk/aws-ec2.WindowsVersion.WINDOWS_SERVER_2008_R2_SP1_JAPANESE_64BIT_SQL_2008_R2_SP3_WEB",
"docs-public-apis:@aws-cdk/aws-ec2.WindowsVersion.WINDOWS_SERVER_2008_R2_SP1_PORTUGESE_BRAZIL_64BIT_BASE"
"docs-public-apis:@aws-cdk/aws-ec2.WindowsVersion.WINDOWS_SERVER_2008_R2_SP1_PORTUGESE_BRAZIL_64BIT_BASE",
"props-default-doc:@aws-cdk/aws-ec2.InterfaceVpcEndpointAttributes.securityGroupId",
"props-default-doc:@aws-cdk/aws-ec2.InterfaceVpcEndpointAttributes.securityGroups"
]
},
"stability": "stable"
Expand Down
23 changes: 21 additions & 2 deletions packages/@aws-cdk/aws-ec2/test/test.vpc-endpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { AnyPrincipal, PolicyStatement } from '@aws-cdk/aws-iam';
import { Stack } from '@aws-cdk/core';
import { Test } from 'nodeunit';
// tslint:disable-next-line:max-line-length
import { GatewayVpcEndpoint, GatewayVpcEndpointAwsService, InterfaceVpcEndpoint, InterfaceVpcEndpointAwsService, SubnetType, Vpc } from '../lib';
import { GatewayVpcEndpoint, GatewayVpcEndpointAwsService, InterfaceVpcEndpoint, InterfaceVpcEndpointAwsService, SecurityGroup, SubnetType, Vpc } from '../lib';

export = {
'gateway endpoint': {
Expand Down Expand Up @@ -276,7 +276,7 @@ export = {

// WHEN
const importedEndpoint = InterfaceVpcEndpoint.fromInterfaceVpcEndpointAttributes(stack2, 'ImportedEndpoint', {
securityGroupId: 'security-group-id',
securityGroups: [SecurityGroup.fromSecurityGroupId(stack2, 'SG', 'security-group-id')],
vpcEndpointId: 'vpc-endpoint-id',
port: 80
});
Expand All @@ -288,6 +288,25 @@ export = {
}));
test.deepEqual(importedEndpoint.vpcEndpointId, 'vpc-endpoint-id');

test.done();
},

'with existing security groups'(test: Test) {
// GIVEN
const stack = new Stack();
const vpc = new Vpc(stack, 'VpcNetwork');

// WHEN
vpc.addInterfaceEndpoint('EcrDocker', {
service: InterfaceVpcEndpointAwsService.ECR_DOCKER,
securityGroups: [SecurityGroup.fromSecurityGroupId(stack, 'SG', 'existing-id')]
});

// THEN
expect(stack).to(haveResource('AWS::EC2::VPCEndpoint', {
SecurityGroupIds: ['existing-id'],
}));

test.done();
}
}
Expand Down

0 comments on commit bda28e8

Please sign in to comment.