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): allow using existing security groups with interface VPC enpoints #4908

Merged
merged 2 commits into from
Nov 8, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
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