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

BREAKING(aws-ec2): SecurityGroup can be used in Connections #582

Merged
merged 3 commits into from
Aug 16, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
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: 4 additions & 7 deletions packages/@aws-cdk/aws-ec2/lib/auto-scaling-group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import autoscaling = require('@aws-cdk/aws-autoscaling');
import iam = require('@aws-cdk/aws-iam');
import sns = require('@aws-cdk/aws-sns');
import cdk = require('@aws-cdk/cdk');
import { AllConnections, AnyIPv4, IConnectionPeer } from './connection';
import { Connections } from './connections';
import { Connections, IConnectable } from './connections';
import { InstanceType } from './instance-types';
import { ClassicLoadBalancer, IClassicLoadBalancerTarget } from './load-balancer';
import { IMachineImageSource, OperatingSystemType } from './machine-image';
import { SecurityGroup } from './security-group';
import { AllConnections, AnyIPv4 } from './security-group-rule';
import { VpcNetworkRef, VpcPlacementStrategy } from './vpc-ref';

/**
Expand Down Expand Up @@ -83,9 +83,7 @@ export interface AutoScalingGroupProps {
*
* The ASG spans all availability zones.
*/
export class AutoScalingGroup extends cdk.Construct implements IClassicLoadBalancerTarget {
public readonly connectionPeer: IConnectionPeer;

export class AutoScalingGroup extends cdk.Construct implements IClassicLoadBalancerTarget, IConnectable {
/**
* The type of OS instances of this fleet are running.
*/
Expand All @@ -110,8 +108,7 @@ export class AutoScalingGroup extends cdk.Construct implements IClassicLoadBalan
super(parent, name);

this.securityGroup = new SecurityGroup(this, 'InstanceSecurityGroup', { vpc: props.vpc });
this.connections = new Connections(this.securityGroup);
this.connectionPeer = this.securityGroup;
this.connections = new Connections({ securityGroup: this.securityGroup });

if (props.allowAllOutbound !== false) {
this.connections.allowTo(new AnyIPv4(), new AllConnections(), 'Outbound traffic allowed by default');
Expand Down
154 changes: 66 additions & 88 deletions packages/@aws-cdk/aws-ec2/lib/connections.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { AnyIPv4, IConnectionPeer, IPortRange } from "./connection";
import { SecurityGroupId } from "./ec2.generated";
import { ISecurityGroup } from "./security-group";
import { SecurityGroupRef } from "./security-group";
import { AnyIPv4, IPortRange, ISecurityGroupRule } from "./security-group-rule";

/**
* The goal of this module is to make possible to write statements like this:
Expand All @@ -24,81 +23,103 @@ export interface IConnectable {
}

/**
* An object that has a Connections object as well as a default port range.
* Properties to intialize a new Connections object
*/
export interface IDefaultConnectable extends IConnectable {
readonly defaultPortRange: IPortRange;
export interface ConnectionsProps {
/**
* Class that represents the rule by which others can connect to this connectable
*
* This object is required, but will be derived from securityGroup if that is passed.
*
* @default Derived from securityGroup if set.
*/
securityGroupRule?: ISecurityGroupRule;

/**
* What securityGroup this object is managing connections for
*
* @default No security
*/
securityGroup?: SecurityGroupRef;

/**
* Default port range for initiating connections to and from this object
*
* @default No default port range
*/
defaultPortRange?: IPortRange;
}

/**
* Manage the security group (firewall) for a connectable resource.
* Manage the allowed network connections for constructs with Security Groups.
*
* This object contains method to allow connections between objects
* that can allow connections.
* Security Groups can be thought of as a firewall for network-connected
* devices. This class makes it easy to allow network connections to and
* from security groups, and between security groups individually. When
* establishing connectivity between security groups, it will automatically
* add rules in both security groups
*
* The .allowDefaultPortXxx() methods are only available if the resource
* this object was created for has the concept of a default port range.
*/
export class Connections {
public readonly connectionPeer: IConnectionPeer;
private readonly securityGroupRule: ISecurityGroupRule;
private readonly securityGroup?: SecurityGroupRef;
private readonly defaultPortRange?: IPortRange;

constructor(private readonly securityGroup: ISecurityGroup, private readonly defaultPortRange?: IPortRange) {
this.connectionPeer = securityGroup;
}
constructor(props: ConnectionsProps) {
if (!props.securityGroupRule && !props.securityGroup) {
throw new Error('Connections: require one of securityGroupRule or securityGroup');
}

/**
* Allow connections to the peer on their default port
*/
public allowToDefaultPort(other: IDefaultConnectable, description: string) {
this.allowTo(other, other.defaultPortRange, description);
this.securityGroupRule = props.securityGroupRule || props.securityGroup!;
this.securityGroup = props.securityGroup;
this.defaultPortRange = props.defaultPortRange;
}

/**
* Allow connections to the peer on the given port
*/
public allowTo(other: IConnectable, portRange: IPortRange, description: string) {
if (this.securityGroup) {
this.securityGroup.addEgressRule(other.connections.connectionPeer, portRange, description);
this.securityGroup.addEgressRule(other.connections.securityGroupRule, portRange, description);
}
if (other.connections.securityGroup) {
other.connections.securityGroup.addIngressRule(this.securityGroupRule, portRange, description);

}
other.connections.allowFrom(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So much cleaner...

new NullConnectable(this.connectionPeer),
portRange,
description);
}

/**
* Allow connections from the peer on the given port
*/
public allowFrom(other: IConnectable, portRange: IPortRange, description: string) {
if (this.securityGroup) {
this.securityGroup.addIngressRule(other.connections.connectionPeer, portRange, description);
this.securityGroup.addIngressRule(other.connections.securityGroupRule, portRange, description);
}
if (other.connections.securityGroup) {
other.connections.securityGroup.addEgressRule(this.securityGroupRule, portRange, description);
}
other.connections.allowTo(
new NullConnectable(this.connectionPeer),
portRange,
description);
}

/**
* Allow hosts inside the security group to connect to each other on the given port
*/
public allowInternally(portRange: IPortRange, description: string) {
if (this.securityGroup) {
this.securityGroup.addIngressRule(this.securityGroup, portRange, description);
this.securityGroup.addIngressRule(this.securityGroupRule, portRange, description);
}
}

/**
* Allow to all IPv4 ranges
*/
public allowToAnyIpv4(portRange: IPortRange, description: string) {
public allowToAnyIPv4(portRange: IPortRange, description: string) {
this.allowTo(new AnyIPv4(), portRange, description);
}

/**
* Allow from any IPv4 ranges
*/
public allowFromAnyIpv4(portRange: IPortRange, description: string) {
public allowFromAnyIPv4(portRange: IPortRange, description: string) {
this.allowFrom(new AnyIPv4(), portRange, description);
}

Expand All @@ -109,7 +130,7 @@ export class Connections {
*/
public allowDefaultPortFrom(other: IConnectable, description: string) {
if (!this.defaultPortRange) {
throw new Error('Cannot call allowDefaultPortFrom(): resource has no default port');
throw new Error('Cannot call allowDefaultPortFrom(): this resource has no default port');
}
this.allowFrom(other, this.defaultPortRange, description);
}
Expand All @@ -119,7 +140,7 @@ export class Connections {
*/
public allowDefaultPortInternally(description: string) {
if (!this.defaultPortRange) {
throw new Error('Cannot call allowDefaultPortInternally(): resource has no default port');
throw new Error('Cannot call allowDefaultPortInternally(): this resource has no default port');
}
this.allowInternally(this.defaultPortRange, description);
}
Expand All @@ -129,62 +150,19 @@ export class Connections {
*/
public allowDefaultPortFromAnyIpv4(description: string) {
if (!this.defaultPortRange) {
throw new Error('Cannot call allowDefaultPortFromAnyIpv4(): resource has no default port');
throw new Error('Cannot call allowDefaultPortFromAnyIpv4(): this resource has no default port');
}
this.allowFromAnyIpv4(this.defaultPortRange, description);
}
}

/**
* Connectable that represents a peer but doesn't modify any security groups
*/
class NullConnectable implements IConnectable {
public readonly connections: Connections;

constructor(connectionPeer: IConnectionPeer) {
this.connections = new SecurityGrouplessConnections(connectionPeer);
}
}

/**
* This object is used by peers who don't allow reverse connections.
*/
export class SecurityGrouplessConnections extends Connections {
constructor(public readonly connectionPeer: IConnectionPeer) {
// Because Connections is no longer an interface but a concrete class,
// we must inherit from it and create it with an instance of ISecurityGroup.
super(new NullSecurityGroup());
}

public allowTo(_other: IConnectable, _connection: IPortRange, _description: string): void {
// Nothing to do
this.allowFromAnyIPv4(this.defaultPortRange, description);
}

public allowFrom(_other: IConnectable, _connection: IPortRange, _description: string): void {
// Nothing to do
}
}

/**
* Instance of ISecurityGroup that's only there for show.
*/
class NullSecurityGroup implements ISecurityGroup {
public securityGroupId: SecurityGroupId = new SecurityGroupId();
public canInlineRule: boolean = false;

public addIngressRule(_peer: IConnectionPeer, _connection: IPortRange, _description: string): void {
// Nothing
}

public addEgressRule(_peer: IConnectionPeer, _connection: IPortRange, _description: string): void {
// Nothing
}

public toIngressRuleJSON() {
return {};
}
/**
* Allow connections to the security group on their default port
*/
public allowToDefaultPort(other: IConnectable, description: string) {
if (other.connections.defaultPortRange === undefined) {
throw new Error('Cannot call alloToDefaultPort(): other resource has no default port');
}

public toEgressRuleJSON() {
return {};
this.allowTo(other, other.connections.defaultPortRange, description);
}
}
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-ec2/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
export * from './auto-scaling-group';
export * from './connection';
export * from './connections';
export * from './instance-types';
export * from './load-balancer';
export * from './machine-image';
export * from './security-group';
export * from './security-group-rule';
export * from './vpc';
export * from './vpc-ref';

Expand Down
31 changes: 17 additions & 14 deletions packages/@aws-cdk/aws-ec2/lib/load-balancer.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import elasticloadbalancing = require('@aws-cdk/aws-elasticloadbalancing');
import cdk = require('@aws-cdk/cdk');
import { AnyIPv4, IConnectionPeer, IPortRange, TcpPort } from './connection';
import { Connections, IConnectable, IDefaultConnectable } from './connections';
import { ISecurityGroup, SecurityGroup } from './security-group';
import { Connections, IConnectable } from './connections';
import { SecurityGroup, SecurityGroupRef } from './security-group';
import { AnyIPv4, IPortRange, TcpPort } from './security-group-rule';
import { VpcNetworkRef, VpcSubnetRef } from './vpc-ref';

/**
Expand Down Expand Up @@ -193,8 +193,6 @@ export class ClassicLoadBalancer extends cdk.Construct implements IConnectable {
*/
public readonly connections: Connections;

public readonly connectionPeer: IConnectionPeer;

/**
* An object controlling specifically the connections for each listener added to this load balancer
*/
Expand All @@ -211,8 +209,7 @@ export class ClassicLoadBalancer extends cdk.Construct implements IConnectable {
super(parent, name);

this.securityGroup = new SecurityGroup(this, 'SecurityGroup', { vpc: props.vpc });
this.connections = new Connections(this.securityGroup);
this.connectionPeer = this.securityGroup;
this.connections = new Connections({ securityGroup: this.securityGroup });

// Depending on whether the ELB has public or internal IPs, pick the right backend subnets
const subnets: VpcSubnetRef[] = props.internetFacing ? props.vpc.publicSubnets : props.vpc.privateSubnets;
Expand Down Expand Up @@ -323,17 +320,23 @@ export class ClassicLoadBalancer extends cdk.Construct implements IConnectable {
}

/**
* Reference to a listener's port just created
* Reference to a listener's port just created.
*
* This implements IConnectable with a default port (the port that an ELB
* listener was just created on) for a given security group so that it can be
* conveniently used just like any Connectable. E.g:
*
* const listener = elb.addListener(...);
*
* This class exists to make it convenient to add port ranges to the load
* balancer's security group just for the port ranges that are involved in the
* listener.
* listener.connections.allowDefaultPortFromAnyIPv4();
* // or
* instance.connections.allowToDefaultPort(listener);
*/
export class ClassicListenerPort implements IDefaultConnectable {
export class ClassicListenerPort implements IConnectable {
public readonly connections: Connections;

constructor(securityGroup: ISecurityGroup, public readonly defaultPortRange: IPortRange) {
this.connections = new Connections(securityGroup, defaultPortRange);
constructor(securityGroup: SecurityGroupRef, defaultPortRange: IPortRange) {
this.connections = new Connections({ securityGroup, defaultPortRange });
}
}

Expand Down
Loading