From d44bee5bc99d5d1b2f9e770885dd1cc9594e561d Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Thu, 13 Sep 2018 15:21:49 +0200 Subject: [PATCH 01/10] Basic classes in place --- packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts | 7 + .../aws-elasticloadbalancingv2/lib/index.ts | 10 + .../lib/listener-certificate.ts | 41 +++ .../lib/listener-ref.ts | 50 ++++ .../lib/listener-rule.ts | 110 +++++++ .../lib/listener.ts | 132 +++++++++ .../lib/load-balancer-ref.ts | 50 ++++ .../lib/load-balancer.ts | 234 +++++++++++++++ .../lib/target-group-ref.ts | 50 ++++ .../lib/target-group.ts | 277 ++++++++++++++++++ .../aws-elasticloadbalancingv2/lib/types.ts | 19 ++ .../aws-elasticloadbalancingv2/lib/util.ts | 49 ++++ .../aws-elasticloadbalancingv2/package.json | 4 +- packages/@aws-cdk/aws-lambda/lib/lambda.ts | 2 +- 14 files changed, 1033 insertions(+), 2 deletions(-) create mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/lib/listener-certificate.ts create mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/lib/listener-ref.ts create mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/lib/listener-rule.ts create mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/lib/listener.ts create mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/lib/load-balancer-ref.ts create mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/lib/load-balancer.ts create mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/lib/target-group-ref.ts create mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/lib/target-group.ts create mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/lib/types.ts create mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/lib/util.ts diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts index 868eacfdd771d..2455d0179ecaf 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts @@ -159,6 +159,13 @@ export abstract class VpcNetworkRef extends Construct implements IDependable { isolatedSubnetNames: iso.names, }; } + + /** + * Return whether the given subnet is one of this VPC's public subnets + */ + public isPublicSubnet(subnet: VpcSubnetRef) { + return this.publicSubnets.indexOf(subnet) > -1; + } } /** diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/index.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/index.ts index 682c0a808371f..b9ea0164b6480 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/index.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/index.ts @@ -1,2 +1,12 @@ // AWS::ElasticLoadBalancingV2 CloudFormation Resources: export * from './elasticloadbalancingv2.generated'; + +export * from './listener'; +export * from './listener-ref'; +export * from './listener-rule'; +export * from './listener-certificate'; +export * from './load-balancer'; +export * from './load-balancer-ref'; +export * from './target-group'; +export * from './target-group-ref'; +export * from './types'; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/listener-certificate.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/listener-certificate.ts new file mode 100644 index 0000000000000..bbd47a8c40ba3 --- /dev/null +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/listener-certificate.ts @@ -0,0 +1,41 @@ +import cdk = require('@aws-cdk/cdk'); +import { cloudformation } from './elasticloadbalancingv2.generated'; +import { ListenerRef } from './listener-ref'; + +/** + * Properties for adding a set of certificates to a listener + */ +export interface ListenerCertificateProps { + /** + * The listener to attach the rule to + */ + listener: ListenerRef; + + /** + * ARNs of certificates to attach + * + * Duplicates are not allowed. + */ + certificateArns: cdk.Arn[]; +} + +/** + * Add certificates to a listener + */ +export class ListenerCertificate extends cdk.Construct implements cdk.IDependable { + /** + * The elements of this resou rce to add ordering dependencies on + */ + public readonly dependencyElements: cdk.IDependable[] = []; + + constructor(parent: cdk.Construct, id: string, props: ListenerCertificateProps) { + super(parent, id); + + const resource = new cloudformation.ListenerCertificateResource(this, 'Resource', { + listenerArn: props.listener.listenerArn, + certificates: props.certificateArns, + }); + + this.dependencyElements.push(resource); + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/listener-ref.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/listener-ref.ts new file mode 100644 index 0000000000000..86029c040e293 --- /dev/null +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/listener-ref.ts @@ -0,0 +1,50 @@ +import cdk = require('@aws-cdk/cdk'); +import { ListenerArn } from './elasticloadbalancingv2.generated'; + +/** + * A listener + */ +export abstract class ListenerRef extends cdk.Construct { + /** + * Import an existing listener + */ + public static import(parent: cdk.Construct, id: string, props: ListenerRefProps) { + return new ImportedListener(parent, id, props); + } + + /** + * ARN of the listener + */ + public abstract readonly listenerArn: ListenerArn; + + /** + * Export this listener + */ + public export(): ListenerRefProps { + return { + listenerArn: new ListenerArn(new cdk.Output(this, 'ListenerArn', { value: this.listenerArn }).makeImportValue()) + }; + } +} + +/** + * Properties to reference an existing listener + */ +export interface ListenerRefProps { + /** + * ARN of the listener + */ + listenerArn: ListenerArn; +} + +/** + * An existing listener + */ +class ImportedListener extends ListenerRef { + public readonly listenerArn: ListenerArn; + constructor(parent: cdk.Construct, id: string, props: ListenerRefProps) { + super(parent, id); + + this.listenerArn = props.listenerArn; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/listener-rule.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/listener-rule.ts new file mode 100644 index 0000000000000..2b4f74b146aef --- /dev/null +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/listener-rule.ts @@ -0,0 +1,110 @@ +import cdk = require('@aws-cdk/cdk'); +import { cloudformation, ListenerRuleArn } from './elasticloadbalancingv2.generated'; +import { ListenerRef } from './listener-ref'; +import { TargetGroupRef } from './target-group-ref'; + +/** + * Properties for defining a listener rule + */ +export interface ListenerRuleProps { + /** + * The listener to attach the rule to + */ + listener: ListenerRef; + + /** + * Priority of the rule + * + * The rule with the lowest priority will be used for every request. + * + * Priorities must be unique. + */ + priority: number; + + /** + * Target groups to forward requests to + */ + targets: TargetGroupRef[]; + + /** + * Rule applies if the requested host matches the indicated host + * + * May contain up to three '*' wildcards. + * + * @see https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-listeners.html#host-conditions + * + * @default No host condition + */ + hostHeader?: string; + + /** + * Rule applies if the requested path matches the given path pattern + * + * May contain up to three '*' wildcards. + * + * @see https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-listeners.html#path-conditions + * + * @default No path condition + */ + pathPattern?: string; +} + +/** + * Define a new listener rule + */ +export class ListenerRule extends cdk.Construct implements cdk.IDependable { + /** + * The ARN of this rule + */ + public readonly listenerRuleArn: ListenerRuleArn; + + /** + * The elements of this rule to add ordering dependencies on + */ + public readonly dependencyElements: cdk.IDependable[] = []; + + private readonly conditions: {[key: string]: string[] | undefined} = {}; + + constructor(parent: cdk.Construct, id: string, props: ListenerRuleProps) { + super(parent, id); + + const resource = new cloudformation.ListenerRuleResource(this, 'Resource', { + listenerArn: props.listener.listenerArn, + priority: props.priority, + conditions: new cdk.Token(() => this.renderConditions()), + actions: props.targets.map(target => ({ + targetGroupArn: target.targetGroupArn, + // The full spectrum of Actions is not supported via CloudFormation; + // only 'forward's currently. + type: 'forward' + })) + }); + + if (props.hostHeader) { + this.setCondition('host-header', [props.hostHeader]); + } + if (props.pathPattern) { + this.setCondition('path-pattern', [props.pathPattern]); + } + + this.dependencyElements.push(resource); + this.listenerRuleArn = resource.ref; + } + + /** + * Add a non-standard condition to this rule + */ + public setCondition(field: string, values: string[] | undefined) { + this.conditions[field] = values; + } + + private renderConditions() { + const ret = []; + for (const [field, values] of Object.entries(this.conditions)) { + if (values !== undefined) { + ret.push({ field, values }); + } + } + return ret; + } +} diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/listener.ts new file mode 100644 index 0000000000000..a0adc6d0eca29 --- /dev/null +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/listener.ts @@ -0,0 +1,132 @@ +import cdk = require('@aws-cdk/cdk'); +import { cloudformation, ListenerArn } from './elasticloadbalancingv2.generated'; +import { ListenerRef } from './listener-ref'; +import { LoadBalancerRef } from './load-balancer-ref'; +import { TargetGroupRef } from './target-group-ref'; +import { Protocol } from './types'; +import { determineProtocolAndPort } from './util'; + +/** + * Properties for defining a listener + */ +export interface ListenerProps { + /** + * The load balancer to attach this listener to + */ + loadBalancer: LoadBalancerRef; + + /** + * The protocol to use + * + * @default Determined from port if known + */ + protocol?: Protocol; + + /** + * The port on which the listener listens for requests. + * + * @default Determined from protocol if known + */ + port?: number; + + /** + * The certificates to use on this listener + */ + certificateArns?: cdk.Arn[]; + + /** + * The security policy that defines which ciphers and protocols are supported. + * + * @default the current predefined security policy. + */ + sslPolicy?: SslPolicy; + + /** + * Default target groups, which will be automatically 'forward'ed to. + */ + defaultTargets: TargetGroupRef[]; +} + +/** + * Define a listener + */ +export class Listener extends ListenerRef { + public readonly listenerArn: ListenerArn; + + constructor(parent: cdk.Construct, id: string, props: ListenerProps) { + super(parent, id); + + const [protocol, port] = determineProtocolAndPort(props.protocol, props.port); + + const resource = new cloudformation.ListenerResource(this, 'Resource', { + loadBalancerArn: props.loadBalancer.loadBalancerArn, + certificates: props.certificateArns, + protocol, + port, + sslPolicy: props.sslPolicy, + defaultActions: props.defaultTargets.map(target => ({ + targetGroupArn: target.targetGroupArn, + // The full spectrum of Actions is not supported via CloudFormation; + // only 'forward's currently. + type: 'forward' + })) + }); + + this.listenerArn = resource.ref; + } +} + +/** + * Elastic Load Balancing provides the following security policies for Application Load Balancers + * + * We recommend the Recommended policy for general use. You can + * use the ForwardSecrecy policy if you require Forward Secrecy + * (FS). + * + * You can use one of the TLS policies to meet compliance and security + * standards that require disabling certain TLS protocol versions, or to + * support legacy clients that require deprecated ciphers. + * + * @see https://docs.aws.amazon.com/elasticloadbalancing/latest/application/create-https-listener.html + */ +export enum SslPolicy { + /** + * The recommended security policy + */ + Recommended = 'ELBSecurityPolicy-2016-08', + + /** + * Forward secrecy ciphers only + */ + ForwardSecrecy = 'ELBSecurityPolicy-FS-2018-06', + + /** + * TLS1.2 only and no SHA ciphers + */ + TLS12 = 'ELBSecurityPolicy-TLS-1-2-2017-01', + + /** + * TLS1.2 only with all ciphers + */ + TLS12Ext = 'ELBSecurityPolicy-TLS-1-2-Ext-2018-06', + + /** + * TLS1.1 and higher with all ciphers + */ + TLS11 = 'ELBSecurityPolicy-TLS-1-1-2017-01', + + /** + * Support for DES-CBC3-SHA + * + * Do not use this security policy unless you must support a legacy client + * that requires the DES-CBC3-SHA cipher, which is a weak cipher. + */ + Legacy = 'ELBSecurityPolicy-TLS-1-0-2015-04', +} + +/** + * + */ +export interface Action { + targetGroup: TargetGroupRef; +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/load-balancer-ref.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/load-balancer-ref.ts new file mode 100644 index 0000000000000..835afaa73ffd7 --- /dev/null +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/load-balancer-ref.ts @@ -0,0 +1,50 @@ +import cdk = require('@aws-cdk/cdk'); +import { LoadBalancerArn } from './elasticloadbalancingv2.generated'; + +/** + * A load balancer + */ +export abstract class LoadBalancerRef extends cdk.Construct { + /** + * Import an existing Load Balancer + */ + public static import(parent: cdk.Construct, id: string, props: LoadBalancerRefProps) { + return new ImportedLoadBalancer(parent, id, props); + } + + /** + * ARN of the load balancer + */ + public abstract readonly loadBalancerArn: LoadBalancerArn; + + /** + * Export this load balancer + */ + public export(): LoadBalancerRefProps { + return { + loadBalancerArn: new LoadBalancerArn(new cdk.Output(this, 'LoadBalancerArn', { value: this.loadBalancerArn }).makeImportValue()) + }; + } +} + +/** + * Properties to reference an existing load balancer + */ +export interface LoadBalancerRefProps { + /** + * ARN of the load balancer + */ + loadBalancerArn: LoadBalancerArn; +} + +/** + * An existing load balancer + */ +class ImportedLoadBalancer extends LoadBalancerRef { + public readonly loadBalancerArn: LoadBalancerArn; + constructor(parent: cdk.Construct, id: string, props: LoadBalancerRefProps) { + super(parent, id); + + this.loadBalancerArn = props.loadBalancerArn; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/load-balancer.ts new file mode 100644 index 0000000000000..3071661d5ee06 --- /dev/null +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/load-balancer.ts @@ -0,0 +1,234 @@ +import ec2 = require('@aws-cdk/aws-ec2'); +import s3 = require('@aws-cdk/aws-s3'); +import cdk = require('@aws-cdk/cdk'); +import { cloudformation, LoadBalancerArn, LoadBalancerCanonicalHostedZoneId, + LoadBalancerDnsName, LoadBalancerFullName, LoadBalancerName } from './elasticloadbalancingv2.generated'; +import { LoadBalancerRef } from './load-balancer-ref'; +import { Attributes, renderAttributes } from './util'; + +/** + * Properties to define a load balancer + */ +export interface LoadBalancerProps { + /** + * Type of the load balancer + * + * @default Application + */ + type?: LoadBalancerType; + + /** + * Name of the load balancer + * + * @default Automatically generated name + */ + loadBalancerName?: string; + + /** + * The VPC network to place the load balancer in + */ + vpc: ec2.VpcNetworkRef; + + /** + * Whether the load balancer has an internet-routable address + * + * @default false + */ + internetFacing?: boolean; + + /** + * Where in the VPC to place the load balancer + * + * @default Public subnets if internetFacing, otherwise private subnets + */ + vpcPlacement?: ec2.VpcPlacementStrategy; + + /** + * Security group to associate with this load balancer + * + * @default A security group is created + */ + securityGroup?: ec2.SecurityGroupRef; + + /** + * The type of IP addresses to use + * + * Only applies to application load balancers. + * + * @default IpAddressType.Ipv4 + */ + ipAddressType?: IpAddressType; + + /** + * Indicates whether deletion protection is enabled. + * + * @default false + */ + deletionProtection?: boolean; + + /** + * Indicates whether HTTP/2 is enabled. + * + * @default true + */ + http2Enabled?: boolean; + + /** + * The load balancer idle timeout, in seconds + * + * @default 60 + */ + idleTimeoutSecs?: number; + + /** + * Indicates whether cross-zone load balancing is enabled. + * + * @default false + */ + crossZoneEnabled?: boolean; +} + +/** + * Define a load balancer + */ +export class LoadBalancer extends LoadBalancerRef implements ec2.IConnectable { + public readonly canonicalHostedZoneId: LoadBalancerCanonicalHostedZoneId; + public readonly dnsName: LoadBalancerDnsName; + public readonly fullName: LoadBalancerFullName; + public readonly loadBalancerName: LoadBalancerName; + public readonly loadBalancerArn: LoadBalancerArn; + public readonly connections: ec2.Connections; + public readonly type: LoadBalancerType; + private readonly attributes: Attributes = {}; + + constructor(parent: cdk.Construct, id: string, props: LoadBalancerProps) { + super(parent, id); + + this.type = props.type || LoadBalancerType.Application; + + if (this.type !== LoadBalancerType.Application && props.ipAddressType) { + throw new Error('ipAddressType can only be supplied for application load balancers'); + } + + const securityGroup = props.securityGroup || new ec2.SecurityGroup(this, 'SecurityGroup', { + vpc: props.vpc, + description: `Automatically created Security Group for ELB ${this.uniqueId}` + }); + const internetFacing = ifUndefined(props.internetFacing, false); + + const subnets = props.vpc.subnets(ifUndefined(props.vpcPlacement, + { subnetsToUse: internetFacing ? ec2.SubnetType.Public : ec2.SubnetType.Private })); + + const resource = new cloudformation.LoadBalancerResource(this, 'Resource', { + type: this.type, + loadBalancerName: props.loadBalancerName, + subnets: subnets.map(s => s.subnetId), + securityGroups: [securityGroup.securityGroupId], + scheme: internetFacing ? 'internet-facing' : 'internal', + ipAddressType: props.ipAddressType, + loadBalancerAttributes: new cdk.Token(() => renderAttributes(this.attributes)), + }); + + if (props.deletionProtection) { this.attributes['deletion_protection.enabled'] = 'true'; } + if (props.http2Enabled === false) { this.attributes['routing.http2.enabled'] = 'false'; } + if (props.idleTimeoutSecs !== undefined) { this.attributes['idle_timeout.timeout_seconds'] = props.idleTimeoutSecs.toString(); } + if (props.crossZoneEnabled) { this.attributes['load_balancing.cross_zone.enabled'] = 'true'; } + + this.connections = new ec2.Connections({ securityGroup }); + this.canonicalHostedZoneId = resource.loadBalancerCanonicalHostedZoneId; + this.dnsName = resource.loadBalancerDnsName; + this.fullName = resource.loadBalancerFullName; + this.loadBalancerName = resource.loadBalancerName; + this.loadBalancerArn = resource.ref; + } + + /** + * Enable access logging for this load balancer + */ + public logAccessLogs(bucket: s3.BucketRef, prefix?: string) { + this.attributes['access_logs.s3.enabled'] = 'true'; + this.attributes['access_logs.s3.bucket'] = bucket.bucketName.toString(); + this.attributes['access_logs.s3.prefix'] = prefix; + + const stack = cdk.Stack.find(this); + + const region = stack.requireRegion('Enable ELBv2 access logging'); + const account = ELBV2_ACCOUNTS[region]; + if (!account) { + throw new Error(`Cannot enable access logging; don't know ELBv2 account for region ${region}`); + } + + // FIXME: can't use grantPut() here because that only takes IAM objects, not arbitrary principals + bucket.addToResourcePolicy(new cdk.PolicyStatement() + .addPrincipal(new cdk.AccountPrincipal(account)) + .addAction('s3:PutObject') + .addResource(bucket.arnForObjects(prefix || '', '*'))); + } + + /** + * Set a non-standard attribute on the load balancer + * + * @see https://docs.aws.amazon.com/elasticloadbalancing/latest/application/application-load-balancers.html#load-balancer-attributes + */ + public setAttribute(key: string, value: string | undefined) { + this.attributes[key] = value; + } + +} + +/** + * The type of the load balancer + */ +export enum LoadBalancerType { + /** + * An application load balancer + */ + Application = 'application', + + /** + * A network load balancer + */ + Network = 'network', +} + +/** + * What kind of addresses to allocate to the load balancer + */ +export enum IpAddressType { + /** + * Allocate IPv4 addresses + */ + Ipv4 = 'ipv4', + + /** + * Allocate both IPv4 and IPv6 addresses + */ + DualStack = 'dualstack', +} + +function ifUndefined(x: T | undefined, def: T) { + return x !== undefined ? x : def; +} + +// https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-access-logs.html#access-logging-bucket-permissions +const ELBV2_ACCOUNTS: {[region: string]: string } = { + 'us-east-1': '127311923021', + 'us-east-2': '033677994240', + 'us-west-1': '027434742980', + 'us-west-2': '797873946194', + 'ca-central-1': '985666609251', + 'eu-central-1': '054676820928', + 'eu-west-1': '156460612806', + 'eu-west-2': '652711504416', + 'eu-west-3': '009996457667', + 'ap-northeast-1': '582318560864', + 'ap-northeast-2': '600734575887', + 'ap-northeast-3': '383597477331', + 'ap-southeast-1': '114774131450', + 'ap-southeast-2': '783225319266', + 'ap-south-1': '718504428378', + 'sa-east-1': '507241528517', + 'us-gov-west-1': '048591011584', + 'cn-north-1': '638102146993', + 'cn-northwest-1': '037604701340', +}; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/target-group-ref.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/target-group-ref.ts new file mode 100644 index 0000000000000..7b9cabde80be2 --- /dev/null +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/target-group-ref.ts @@ -0,0 +1,50 @@ +import cdk = require('@aws-cdk/cdk'); +import { TargetGroupArn } from './elasticloadbalancingv2.generated'; + +/** + * A target group + */ +export abstract class TargetGroupRef extends cdk.Construct { + /** + * Import an existing target group + */ + public static import(parent: cdk.Construct, id: string, props: TargetGroupRefProps) { + return new ImportedTargetGroup(parent, id, props); + } + + /** + * ARN of the target group + */ + public abstract readonly targetGroupArn: TargetGroupArn; + + /** + * Export this target group + */ + public export(): TargetGroupRefProps { + return { + targetGroupArn: new TargetGroupArn(new cdk.Output(this, 'TargetGroupArn', { value: this.targetGroupArn }).makeImportValue()) + }; + } +} + +/** + * Properties to reference an existing target group + */ +export interface TargetGroupRefProps { + /** + * ARN of the target group + */ + targetGroupArn: TargetGroupArn; +} + +/** + * An existing load balancer + */ +class ImportedTargetGroup extends TargetGroupRef { + public readonly targetGroupArn: TargetGroupArn; + constructor(parent: cdk.Construct, id: string, props: TargetGroupRefProps) { + super(parent, id); + + this.targetGroupArn = props.targetGroupArn; + } +} diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/target-group.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/target-group.ts new file mode 100644 index 0000000000000..e8d3f960f9d0d --- /dev/null +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/target-group.ts @@ -0,0 +1,277 @@ +import ec2 = require('@aws-cdk/aws-ec2'); +import cdk = require('@aws-cdk/cdk'); +import { cloudformation, TargetGroupArn, TargetGroupFullName, TargetGroupName } from './elasticloadbalancingv2.generated'; +import { Protocol } from './types'; +import { Attributes, determineProtocolAndPort, renderAttributes } from './util'; + +export interface TargetGroupProps { + /** + * The approximate number of seconds between health checks for an individual target. + * + * @default 30 + */ + healthCheckIntervalSecs?: number; + + /** + * The ping path destination where Elastic Load Balancing sends health check requests. + * + * @default / + */ + healthCheckPath?: string; + + /** + * The port that the load balancer uses when performing health checks on the targets. + * + * @default 'traffic-port' + */ + healthCheckPort?: string; + + /** + * The protocol the load balancer uses when performing health checks on targets. + * + * The TCP protocol is supported only if the protocol of the target group + * is TCP. + * + * @default HTTP for ALBs, TCP for NLBs + */ + healthCheckProtocol?: Protocol; + + /** + * The amount of time, in seconds, during which no response from a target means a failed health check. + * + * For Application Load Balancers, the range is 2–60 seconds and the + * default is 5 seconds. For Network Load Balancers, this is 10 seconds for + * TCP and HTTPS health checks and 6 seconds for HTTP health checks. + * + * @default 5 for ALBs, 10 or 6 for NLBs + */ + healthCheckTimeoutSeconds?: number; + + /** + * The number of consecutive health checks successes required before considering an unhealthy target healthy. + * + * For Application Load Balancers, the default is 5. For Network Load Balancers, the default is 3. + * + * @default 5 for ALBs, 3 for NLBs + */ + healthyThresholdCount?: number; + + /** + * The number of consecutive health check failures required before considering a target unhealthy. + * + * For Application Load Balancers, the default is 2. For Network Load + * Balancers, this value must be the same as the healthy threshold count. + * + * @default 2 + */ + unhealthyThresholdCount?: number; + + /** + * The name of the target group. + * + * This name must be unique per region per account, can have a maximum of + * 32 characters, must contain only alphanumeric characters or hyphens, and + * must not begin or end with a hyphen. + * + * @default Automatically generated + */ + targetGroupName?: string, + + /** + * The port on which the targets receive traffic. + * + * This port is used unless you specify a port override when registering the target. + * + * @default Determined from Protocol if known. + */ + port?: number; + + /** + * The protocol to use for routing traffic to the targets. + * + * For Application Load Balancers, the supported protocols are HTTP and + * HTTPS. For Network Load Balancers, the supported protocol is TCP. + * + * @default Determined from Port if known. + */ + protocol?: Protocol; + + /** + * The type of target that you must specify when registering targets with this target group. + * + * The possible values are instance (targets are specified by instance ID) + * or ip (targets are specified by IP address). The default is instance. + * You can't specify targets for a target group using both instance IDs and + * IP addresses. + * + * If the target type is ip, specify IP addresses from the subnets of the + * virtual private cloud (VPC) for the target group, the RFC 1918 range + * (10.0.0.0/8, 172.16.0.0/12, and 192.168.0.0/16), and the RFC 6598 range + * (100.64.0.0/10). You can't specify publicly routable IP addresses. + * + * @default Instance + */ + targetType?: TargetType; + + /** + * The targets to add to this target group. + */ + targets?: TargetDescription[]; + + /** + * HTTP code to use when checking for a successful response from a target. + * + * For Application Load Balancers, you can specify values between 200 and + * 499, and the default value is 200. You can specify multiple values (for + * example, "200,202") or a range of values (for example, "200-299"). + */ + healthyHttpCodes?: string; + + /** + * The virtual private cloud (VPC). + */ + vpc: ec2.VpcNetwork; + + /** + * The amount of time for Elastic Load Balancing to wait before deregistering a target. + * + * The range is 0–3600 seconds. + * + * @default 300 + */ + deregistrationDelaySec?: number; + + /** + * The time period during which the load balancer sends a newly registered target a linearly increasing share of the traffic to the target group. + * + * The range is 30–900 seconds (15 minutes). + * + * @default 0 + */ + slowStartSec?: number; + + /** + * The stickiness cookie expiration period. + * + * Setting this value enables load balancer stickiness. + * + * After this period, the cookie is considered stale. The minimum value is + * 1 second and the maximum value is 7 days (604800 seconds). + * + * @default 86400 (1 day) + */ + stickinessCookieDurationSec?: number; +} + +export interface TargetDescription { + /** + * An Availability Zone or all. + * + * This determines whether the target receives traffic from the load + * balancer nodes in the specified Availability Zone or from all enabled + * Availability Zones for the load balancer. + * + * This parameter is not supported if the target type of the target group + * is instance. If the IP address is in a subnet of the VPC for the target + * group, the Availability Zone is automatically detected and this + * parameter is optional. If the IP address is outside the VPC, this + * parameter is required. + * + * With an Application Load Balancer, if the IP address is outside the VPC + * for the target group, the only supported value is all. + * + * @default Automatic + */ + availabilityZone?: string; + + /** + * The ID of the target. + * + * If the target type of the target group is instance, specify an instance + * ID. If the target type is ip, specify an IP address. + */ + id: string; + + /** + * Override the default port on which the target is listening + */ + port?: number; +} + +/** + * Define the target of a load balancer + */ +export class TargetGroup extends cdk.Construct { + public readonly targetGroupArn: TargetGroupArn; + public readonly targetGroupFullName: TargetGroupFullName; + public readonly targetGroupName: TargetGroupName; + + private readonly attributes: Attributes = {}; + + constructor(parent: cdk.Construct, id: string, props: TargetGroupProps) { + super(parent, id); + + if (props.deregistrationDelaySec !== undefined) { + this.setAttribute('deregistration_delay.timeout_seconds', props.deregistrationDelaySec.toString()); + } + if (props.slowStartSec !== undefined) { + this.setAttribute('slow_start.duration_seconds', props.slowStartSec.toString()); + } + if (props.stickinessCookieDurationSec !== undefined) { + this.setAttribute('stickiness.enabled', 'true'); + this.setAttribute('stickiness.type', 'lb_cookie'); + this.setAttribute('stickiness.lb_cookie.duration_seconds', props.stickinessCookieDurationSec.toString()); + } + + const [protocol, port] = determineProtocolAndPort(props.protocol, props.port); + + const resource = new cloudformation.TargetGroupResource(this, 'Resource', { + targetGroupName: props.targetGroupName, + protocol, + port, + targetGroupAttributes: new cdk.Token(() => renderAttributes(this.attributes)), + targetType: props.targetType, + targets: props.targets, + vpcId: props.vpc.vpcId, + + // HEALTH CHECK + healthCheckIntervalSeconds: props.healthCheckIntervalSecs, + healthCheckPath: props.healthCheckPath, + healthCheckPort: props.healthCheckPort, + healthCheckProtocol: props.healthCheckProtocol, + healthCheckTimeoutSeconds: props.healthCheckTimeoutSeconds, + healthyThresholdCount: props.healthyThresholdCount, + matcher: props.healthyHttpCodes === undefined ? undefined : { + httpCode: props.healthyHttpCodes + }, + }); + + this.targetGroupArn = resource.ref; + this.targetGroupFullName = resource.targetGroupFullName; + this.targetGroupName = resource.targetGroupName; + } + + /** + * Set a non-standard attribute on the target group + * + * @see https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-target-groups.html#target-group-attributes + */ + public setAttribute(key: string, value: string | undefined) { + this.attributes[key] = value; + } +} + +/** + * How to interpret the load balancing target identifiers + */ +export enum TargetType { + /** + * Targets identified by instance ID + */ + Instance = 'instance', + + /** + * Targets identified by IP address + */ + Ip = 'ip' +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/types.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/types.ts new file mode 100644 index 0000000000000..ac75ee4548946 --- /dev/null +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/types.ts @@ -0,0 +1,19 @@ +/** + * Load balancing protocol + */ +export enum Protocol { + /** + * TCP + */ + Tcp = 'TCP', + + /** + * HTTP + */ + Http = 'HTTP', + + /** + * HTTPS + */ + Https = 'HTTPS' +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/util.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/util.ts new file mode 100644 index 0000000000000..536b3372dfbff --- /dev/null +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/util.ts @@ -0,0 +1,49 @@ +import { Protocol } from "./types"; + +export type Attributes = {[key: string]: string | undefined}; +export function renderAttributes(attributes: Attributes) { + const ret: any = {}; + for (const [key, value] of Object.entries(attributes)) { + if (value !== undefined) { + ret.push({ key, value }); + } + } + return ret; +} + +export function defaultPortForProtocol(proto: Protocol): number { + switch (proto) { + case Protocol.Http: return 80; + case Protocol.Https: return 443; + case Protocol.Tcp: throw new Error("Can't determine default port for protocol Tcp; please supply a port"); + default: + throw new Error(`Unrecognized protocol: ${proto}`); + } +} + +export function defaultProtocolForPort(port: number): Protocol { + switch (port) { + case 80: + case 8080: + case 8008: + return Protocol.Http; + + case 443: + case 8443: + return Protocol.Https; + + default: + throw new Error(`Don't know default protocol for port: ${port}; please supply a protocol`); + } +} + +export function determineProtocolAndPort(protocol: Protocol | undefined, port: number | undefined): [Protocol, number] { + if (protocol === undefined && port === undefined) { + throw new Error('Supply at least one of protocol and port'); + } + + if (protocol === undefined) { protocol = defaultProtocolForPort(port!); } + if (port === undefined) { port = defaultPortForProtocol(protocol!); } + + return [protocol, port]; +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json b/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json index 277bb368f94ed..e93b9236759ab 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json @@ -58,7 +58,9 @@ "pkglint": "^0.9.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.9.0" + "@aws-cdk/cdk": "^0.9.0", + "@aws-cdk/aws-ec2": "^0.9.0", + "@aws-cdk/aws-s3": "^0.9.0" }, "homepage": "https://github.com/awslabs/aws-cdk" } diff --git a/packages/@aws-cdk/aws-lambda/lib/lambda.ts b/packages/@aws-cdk/aws-lambda/lib/lambda.ts index b2c8e917901e3..4ddfd90d9a967 100644 --- a/packages/@aws-cdk/aws-lambda/lib/lambda.ts +++ b/packages/@aws-cdk/aws-lambda/lib/lambda.ts @@ -300,7 +300,7 @@ export class Function extends FunctionRef { // won't work because the ENIs don't get a Public IP. const subnets = props.vpc.subnets(props.vpcPlacement); for (const subnet of subnets) { - if (props.vpc.publicSubnets.indexOf(subnet) > -1) { + if (props.vpc.isPublicSubnet(subnet)) { throw new Error('Not possible to place Lambda Functions in a Public subnet'); } } From 314225c778f7101ed3609adf768b0ee63df579a9 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Fri, 14 Sep 2018 14:13:32 +0200 Subject: [PATCH 02/10] Make ASGs work with ELBv2 --- ....asg-w-classic-loadbalancer.expected.json} | 0 ...ts => integ.asg-w-classic-loadbalancer.ts} | 0 .../aws-autoscaling/test/integ.asg-w-elbv2.ts | 33 +++++++++++++++++++ 3 files changed, 33 insertions(+) rename packages/@aws-cdk/aws-autoscaling/test/{integ.asg-w-loadbalancer.expected.json => integ.asg-w-classic-loadbalancer.expected.json} (100%) rename packages/@aws-cdk/aws-autoscaling/test/{integ.asg-w-loadbalancer.ts => integ.asg-w-classic-loadbalancer.ts} (100%) create mode 100644 packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-elbv2.ts diff --git a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-loadbalancer.expected.json b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-classic-loadbalancer.expected.json similarity index 100% rename from packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-loadbalancer.expected.json rename to packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-classic-loadbalancer.expected.json diff --git a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-loadbalancer.ts b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-classic-loadbalancer.ts similarity index 100% rename from packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-loadbalancer.ts rename to packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-classic-loadbalancer.ts diff --git a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-elbv2.ts b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-elbv2.ts new file mode 100644 index 0000000000000..26c346d975563 --- /dev/null +++ b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-elbv2.ts @@ -0,0 +1,33 @@ +#!/usr/bin/env node +import ec2 = require('@aws-cdk/aws-ec2'); +import cdk = require('@aws-cdk/cdk'); +import autoscaling = require('../lib'); + +const app = new cdk.App(process.argv); +const stack = new cdk.Stack(app, 'aws-cdk-ec2-integ'); + +const vpc = new ec2.VpcNetwork(stack, 'VPC', { + maxAZs: 3 +}); + +const asg = new autoscaling.AutoScalingGroup(stack, 'Fleet', { + vpc, + instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Micro), + machineImage: new ec2.AmazonLinuxImage(), +}); + +new ec2.ClassicLoadBalancer(stack, 'LB', { + vpc, + internetFacing: true, + listeners: [{ + externalPort: 80, + allowConnectionsFrom: [new ec2.AnyIPv4()] + }], + healthCheck: { + port: 80 + }, + targets: [asg] +}); + +process.stdout.write(app.run()); + From b1db682f70b660ab453de6bc117447d8cf43a66c Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Mon, 17 Sep 2018 10:50:09 +0200 Subject: [PATCH 03/10] Make security groups work BREAKING CHANGE: changes identifiers of Security Group rules --- .../aws-autoscaling/lib/auto-scaling-group.ts | 19 +- .../@aws-cdk/aws-autoscaling/package.json | 1 + .../aws-autoscaling/test/integ.asg-w-elbv2.ts | 28 ++- packages/@aws-cdk/aws-ec2/lib/connections.ts | 61 ++++- .../aws-ec2/lib/security-group-rule.ts | 31 +++ .../@aws-cdk/aws-ec2/lib/security-group.ts | 57 +++-- .../lib/listener-ref.ts | 50 +++- .../lib/listener-rule.ts | 46 +++- .../lib/listener.ts | 54 ++-- .../lib/load-balancer-ref.ts | 23 +- .../lib/load-balancer.ts | 3 +- .../lib/target-group-ref.ts | 30 ++- .../lib/target-group.ts | 235 +++++++++++++----- .../test/test.security-groups.ts | 17 ++ 14 files changed, 514 insertions(+), 141 deletions(-) create mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/test/test.security-groups.ts diff --git a/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts b/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts index 26bb60f910fef..8bcf34b9b37ee 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts @@ -1,5 +1,6 @@ import ec2 = require('@aws-cdk/aws-ec2'); import elb = require('@aws-cdk/aws-elasticloadbalancing'); +import elbv2 = require('@aws-cdk/aws-elasticloadbalancingv2'); import iam = require('@aws-cdk/aws-iam'); import sns = require('@aws-cdk/aws-sns'); import cdk = require('@aws-cdk/cdk'); @@ -136,7 +137,7 @@ export interface AutoScalingGroupProps { * * The ASG spans all availability zones. */ -export class AutoScalingGroup extends cdk.Construct implements elb.ILoadBalancerTarget, ec2.IConnectable { +export class AutoScalingGroup extends cdk.Construct implements elb.ILoadBalancerTarget, ec2.IConnectable, elbv2.ILoadBalancerTarget { /** * The type of OS instances of this fleet are running. */ @@ -157,6 +158,7 @@ export class AutoScalingGroup extends cdk.Construct implements elb.ILoadBalancer private readonly securityGroup: ec2.SecurityGroupRef; private readonly securityGroups: ec2.SecurityGroupRef[] = []; private readonly loadBalancerNames: cdk.Token[] = []; + private readonly targetGroupArns: cdk.Token[] = []; constructor(parent: cdk.Construct, name: string, props: AutoScalingGroupProps) { super(parent, name); @@ -206,7 +208,8 @@ export class AutoScalingGroup extends cdk.Construct implements elb.ILoadBalancer maxSize: maxSize.toString(), desiredCapacity: desiredCapacity.toString(), launchConfigurationName: launchConfig.ref, - loadBalancerNames: new cdk.Token(() => this.loadBalancerNames), + loadBalancerNames: new cdk.Token(() => this.loadBalancerNames.length > 0 ? this.loadBalancerNames : undefined), + targetGroupArns: new cdk.Token(() => this.targetGroupArns.length > 0 ? this.targetGroupArns : undefined), }; if (props.notificationsTopic) { @@ -241,10 +244,22 @@ export class AutoScalingGroup extends cdk.Construct implements elb.ILoadBalancer this.securityGroups.push(securityGroup); } + /** + * Attach to a classic load balancer + */ public attachToClassicLB(loadBalancer: elb.LoadBalancer): void { this.loadBalancerNames.push(loadBalancer.loadBalancerName); } + /** + * Attach to ELBv2 Target Group + */ + public attachToELBv2TargetGroup(targetGroup: elbv2.TargetGroupRef): elbv2.LoadBalancerTargetProps { + this.targetGroupArns.push(targetGroup.targetGroupArn); + targetGroup.registerConnectable(this); + return { targetType: elbv2.TargetType.SelfRegistering }; + } + /** * Add command to the startup script of fleet instances. * The command must be in the scripting language supported by the fleet's OS (i.e. Linux/Windows). diff --git a/packages/@aws-cdk/aws-autoscaling/package.json b/packages/@aws-cdk/aws-autoscaling/package.json index ddec5bf074189..df338fa821aeb 100644 --- a/packages/@aws-cdk/aws-autoscaling/package.json +++ b/packages/@aws-cdk/aws-autoscaling/package.json @@ -61,6 +61,7 @@ "dependencies": { "@aws-cdk/aws-ec2": "^0.9.1", "@aws-cdk/aws-elasticloadbalancing": "^0.9.1", + "@aws-cdk/aws-elasticloadbalancingv2": "^0.9.1", "@aws-cdk/aws-iam": "^0.9.1", "@aws-cdk/aws-sns": "^0.9.1", "@aws-cdk/cdk": "^0.9.1" diff --git a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-elbv2.ts b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-elbv2.ts index 26c346d975563..20fda8f06d4a3 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-elbv2.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-elbv2.ts @@ -1,5 +1,6 @@ #!/usr/bin/env node import ec2 = require('@aws-cdk/aws-ec2'); +import elbv2 = require('@aws-cdk/aws-elasticloadbalancingv2'); import cdk = require('@aws-cdk/cdk'); import autoscaling = require('../lib'); @@ -7,7 +8,7 @@ const app = new cdk.App(process.argv); const stack = new cdk.Stack(app, 'aws-cdk-ec2-integ'); const vpc = new ec2.VpcNetwork(stack, 'VPC', { - maxAZs: 3 + maxAZs: 2 }); const asg = new autoscaling.AutoScalingGroup(stack, 'Fleet', { @@ -16,18 +17,21 @@ const asg = new autoscaling.AutoScalingGroup(stack, 'Fleet', { machineImage: new ec2.AmazonLinuxImage(), }); -new ec2.ClassicLoadBalancer(stack, 'LB', { +const lb = new elbv2.LoadBalancer(stack, 'LB', { vpc, - internetFacing: true, - listeners: [{ - externalPort: 80, - allowConnectionsFrom: [new ec2.AnyIPv4()] - }], - healthCheck: { - port: 80 - }, - targets: [asg] + internetFacing: true +}); + +const listener = lb.addListener('Listener', { + port: 80, }); -process.stdout.write(app.run()); +listener.addDefaultTargetGroup(new elbv2.TargetGroup(stack, 'Target', { + vpc, + port: 80, + targets: [asg] +})); + +listener.connections.allowDefaultPortFromAnyIpv4('Open to the world'); +process.stdout.write(app.run()); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/lib/connections.ts b/packages/@aws-cdk/aws-ec2/lib/connections.ts index 38a518c5f73eb..5efe377c30c66 100644 --- a/packages/@aws-cdk/aws-ec2/lib/connections.ts +++ b/packages/@aws-cdk/aws-ec2/lib/connections.ts @@ -86,7 +86,7 @@ export class Connections { /** * Allow connections to the peer on the given port */ - public allowTo(other: IConnectable, portRange: IPortRange, description: string) { + public allowTo(other: IConnectable, portRange: IPortRange, description?: string) { if (this.securityGroup) { this.securityGroup.addEgressRule(other.connections.securityGroupRule, portRange, description); } @@ -99,7 +99,7 @@ export class Connections { /** * Allow connections from the peer on the given port */ - public allowFrom(other: IConnectable, portRange: IPortRange, description: string) { + public allowFrom(other: IConnectable, portRange: IPortRange, description?: string) { if (this.securityGroup) { this.securityGroup.addIngressRule(other.connections.securityGroupRule, portRange, description); } @@ -111,7 +111,7 @@ export class Connections { /** * Allow hosts inside the security group to connect to each other on the given port */ - public allowInternally(portRange: IPortRange, description: string) { + public allowInternally(portRange: IPortRange, description?: string) { if (this.securityGroup) { this.securityGroup.addIngressRule(this.securityGroupRule, portRange, description); } @@ -120,14 +120,14 @@ export class Connections { /** * 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); } @@ -136,7 +136,7 @@ export class Connections { * * Even if the peer has a default port, we will always use our default port. */ - public allowDefaultPortFrom(other: IConnectable, description: string) { + public allowDefaultPortFrom(other: IConnectable, description?: string) { if (!this.defaultPortRange) { throw new Error('Cannot call allowDefaultPortFrom(): this resource has no default port'); } @@ -146,7 +146,7 @@ export class Connections { /** * Allow hosts inside the security group to connect to each other */ - public allowDefaultPortInternally(description: string) { + public allowDefaultPortInternally(description?: string) { if (!this.defaultPortRange) { throw new Error('Cannot call allowDefaultPortInternally(): this resource has no default port'); } @@ -156,7 +156,7 @@ export class Connections { /** * Allow default connections from all IPv4 ranges */ - public allowDefaultPortFromAnyIpv4(description: string) { + public allowDefaultPortFromAnyIpv4(description?: string) { if (!this.defaultPortRange) { throw new Error('Cannot call allowDefaultPortFromAnyIpv4(): this resource has no default port'); } @@ -166,11 +166,54 @@ export class Connections { /** * Allow connections to the security group on their default port */ - public allowToDefaultPort(other: IConnectable, description: string) { + public allowToDefaultPort(other: IConnectable, description?: string) { if (other.connections.defaultPortRange === undefined) { throw new Error('Cannot call alloToDefaultPort(): other resource has no default port'); } this.allowTo(other, other.connections.defaultPortRange, description); } + + /** + * Allow connections from the peer on our default port + * + * Even if the peer has a default port, we will always use our default port. + */ + public allowDefaultPortTo(other: IConnectable, description?: string) { + if (!this.defaultPortRange) { + throw new Error('Cannot call allowDefaultPortTo(): this resource has no default port'); + } + this.allowTo(other, this.defaultPortRange, description); + } + +} + +/** + * An instance of Connections that you can use on importerted classes that need to implement IConnectable + */ +export class ImportedConnections extends Connections { + constructor() { + super({ securityGroupRule: { + canInlineRule: false, + uniqueId: '', + toEgressRuleJSON() { return {}; }, + toIngressRuleJSON() { return {}; }, + } + }); + } + + /** + * Allow connections to the peer on the given port + */ + public allowTo(_other: IConnectable, _portRange: IPortRange, _description?: string) { + // FIXME: Record in metadata that we dropped these permissions + } + + /** + * Allow connections from the peer on the given port + */ + public allowFrom(_other: IConnectable, _portRange: IPortRange, _description?: string) { + // FIXME: Record in metadata that we dropped these permissions + } + } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/lib/security-group-rule.ts b/packages/@aws-cdk/aws-ec2/lib/security-group-rule.ts index 013f30b6d446a..4906aee18cd72 100644 --- a/packages/@aws-cdk/aws-ec2/lib/security-group-rule.ts +++ b/packages/@aws-cdk/aws-ec2/lib/security-group-rule.ts @@ -10,6 +10,11 @@ export interface ISecurityGroupRule { */ readonly canInlineRule: boolean; + /** + * A unique identifier for this connection peer + */ + readonly uniqueId: string; + /** * Produce the ingress rule JSON for the given connection */ @@ -27,8 +32,10 @@ export interface ISecurityGroupRule { export class CidrIPv4 implements ISecurityGroupRule, IConnectable { public readonly canInlineRule = true; public readonly connections: Connections = new Connections({ securityGroupRule: this }); + public readonly uniqueId: string; constructor(private readonly cidrIp: string) { + this.uniqueId = cidrIp; } /** @@ -60,8 +67,10 @@ export class AnyIPv4 extends CidrIPv4 { export class CidrIPv6 implements ISecurityGroupRule, IConnectable { public readonly canInlineRule = true; public readonly connections: Connections = new Connections({ securityGroupRule: this }); + public readonly uniqueId: string; constructor(private readonly cidrIpv6: string) { + this.uniqueId = cidrIpv6; } /** @@ -99,8 +108,10 @@ export class AnyIPv6 extends CidrIPv6 { export class PrefixList implements ISecurityGroupRule, IConnectable { public readonly canInlineRule = true; public readonly connections: Connections = new Connections({ securityGroupRule: this }); + public readonly uniqueId: string; constructor(private readonly prefixListId: string) { + this.uniqueId = prefixListId; } public toIngressRuleJSON(): any { @@ -154,6 +165,10 @@ export class TcpPort implements IPortRange { toPort: this.port }; } + + public toString() { + return `${this.port}`; + } } /** @@ -172,6 +187,10 @@ export class TcpPortFromAttribute implements IPortRange { toPort: this.port }; } + + public toString() { + return '{IndirectPort}'; + } } /** @@ -190,6 +209,10 @@ export class TcpPortRange implements IPortRange { toPort: this.endPort }; } + + public toString() { + return `${this.startPort}-${this.endPort}`; + } } /** @@ -205,6 +228,10 @@ export class TcpAllPorts implements IPortRange { toPort: 65535 }; } + + public toString() { + return 'ALL PORTS'; + } } /** @@ -220,4 +247,8 @@ export class AllConnections implements IPortRange { toPort: -1, }; } + + public toString() { + return 'ALL TRAFFIC'; + } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/lib/security-group.ts b/packages/@aws-cdk/aws-ec2/lib/security-group.ts index 88aceba456bf1..d1a7e68316757 100644 --- a/packages/@aws-cdk/aws-ec2/lib/security-group.ts +++ b/packages/@aws-cdk/aws-ec2/lib/security-group.ts @@ -2,7 +2,6 @@ import { Construct, Output, Token } from '@aws-cdk/cdk'; import { Connections, IConnectable } from './connections'; import { cloudformation, SecurityGroupId, SecurityGroupName, SecurityGroupVpcId } from './ec2.generated'; import { IPortRange, ISecurityGroupRule } from './security-group-rule'; -import { slugify } from './util'; import { VpcNetworkRef } from './vpc-ref'; export interface SecurityGroupRefProps { @@ -32,22 +31,38 @@ export abstract class SecurityGroupRef extends Construct implements ISecurityGro */ public readonly defaultPortRange?: IPortRange; - public addIngressRule(peer: ISecurityGroupRule, connection: IPortRange, description: string) { - new cloudformation.SecurityGroupIngressResource(this, slugify(description), { - groupId: this.securityGroupId, - ...peer.toIngressRuleJSON(), - ...connection.toRuleJSON(), - description - }); + public addIngressRule(peer: ISecurityGroupRule, connection: IPortRange, description?: string) { + const id = `from ${peer.uniqueId}:${connection}`; + if (description === undefined) { + description = id; + } + + // Skip duplicates + if (this.tryFindChild(id) === undefined) { + new cloudformation.SecurityGroupIngressResource(this, id, { + groupId: this.securityGroupId, + ...peer.toIngressRuleJSON(), + ...connection.toRuleJSON(), + description + }); + } } - public addEgressRule(peer: ISecurityGroupRule, connection: IPortRange, description: string) { - new cloudformation.SecurityGroupEgressResource(this, slugify(description), { - groupId: this.securityGroupId, - ...peer.toEgressRuleJSON(), - ...connection.toRuleJSON(), - description - }); + public addEgressRule(peer: ISecurityGroupRule, connection: IPortRange, description?: string) { + const id = `to ${peer.uniqueId}:${connection}`; + if (description === undefined) { + description = id; + } + + // Skip duplicates + if (this.tryFindChild(id) === undefined) { + new cloudformation.SecurityGroupEgressResource(this, id, { + groupId: this.securityGroupId, + ...peer.toEgressRuleJSON(), + ...connection.toRuleJSON(), + description + }); + } } public toIngressRuleJSON(): any { @@ -139,12 +154,16 @@ export class SecurityGroup extends SecurityGroupRef { this.vpcId = this.securityGroup.securityGroupVpcId; } - public addIngressRule(peer: ISecurityGroupRule, connection: IPortRange, description: string) { + public addIngressRule(peer: ISecurityGroupRule, connection: IPortRange, description?: string) { if (!peer.canInlineRule || !connection.canInlineRule) { super.addIngressRule(peer, connection, description); return; } + if (description === undefined) { + description = `from ${peer.uniqueId}:${connection}`; + } + this.addDirectIngressRule({ ...peer.toIngressRuleJSON(), ...connection.toRuleJSON(), @@ -152,12 +171,16 @@ export class SecurityGroup extends SecurityGroupRef { }); } - public addEgressRule(peer: ISecurityGroupRule, connection: IPortRange, description: string) { + public addEgressRule(peer: ISecurityGroupRule, connection: IPortRange, description?: string) { if (!peer.canInlineRule || !connection.canInlineRule) { super.addEgressRule(peer, connection, description); return; } + if (description === undefined) { + description = `from ${peer.uniqueId}:${connection}`; + } + this.addDirectEgressRule({ ...peer.toIngressRuleJSON(), ...connection.toRuleJSON(), diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/listener-ref.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/listener-ref.ts index 86029c040e293..6b0b7f7eb447f 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/listener-ref.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/listener-ref.ts @@ -1,22 +1,42 @@ +import ec2 = require('@aws-cdk/aws-ec2'); import cdk = require('@aws-cdk/cdk'); import { ListenerArn } from './elasticloadbalancingv2.generated'; +import { BaseListenerRuleProps, ListenerRule } from './listener-rule'; +import { TargetGroupRef } from './target-group-ref'; /** * A listener */ -export abstract class ListenerRef extends cdk.Construct { +export abstract class ListenerRef extends cdk.Construct implements ec2.IConnectable { /** * Import an existing listener */ - public static import(parent: cdk.Construct, id: string, props: ListenerRefProps) { + public static import(parent: cdk.Construct, id: string, props: ListenerRefProps): ListenerRef { return new ImportedListener(parent, id, props); } + /** + * Class to give to target group so it can access private functions + */ + private static Internals = class implements IListenerInternals { + constructor(private readonly listener: ListenerRef) { + } + + public registerConnectable(connectable: ec2.IConnectable): void { + this.listener.connections.allowDefaultPortTo(connectable, 'Load balancer to target'); + } + }; + /** * ARN of the listener */ public abstract readonly listenerArn: ListenerArn; + /** + * Connections for this listener + */ + public readonly abstract connections: ec2.Connections; + /** * Export this listener */ @@ -25,6 +45,23 @@ export abstract class ListenerRef extends cdk.Construct { listenerArn: new ListenerArn(new cdk.Output(this, 'ListenerArn', { value: this.listenerArn }).makeImportValue()) }; } + + /** + * Add a rule to this listener + */ + public addRule(id: string, props: BaseListenerRuleProps) { + return new ListenerRule(this, id, { + listener: this, + ...props + }); + } + + /** + * Register a target group with this listener + */ + protected registerTargetGroup(targetGroup: TargetGroupRef) { + targetGroup.bindToListener(new ListenerRef.Internals(this)); + } } /** @@ -42,9 +79,18 @@ export interface ListenerRefProps { */ class ImportedListener extends ListenerRef { public readonly listenerArn: ListenerArn; + public readonly connections: ec2.Connections = new ec2.ImportedConnections(); + constructor(parent: cdk.Construct, id: string, props: ListenerRefProps) { super(parent, id); this.listenerArn = props.listenerArn; } +} + +/** + * Parts of the listener that only Target Groups should have access to + */ +export interface IListenerInternals { + registerConnectable(connectable: ec2.IConnectable): void; } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/listener-rule.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/listener-rule.ts index 2b4f74b146aef..19e3914d6b9fe 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/listener-rule.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/listener-rule.ts @@ -4,14 +4,9 @@ import { ListenerRef } from './listener-ref'; import { TargetGroupRef } from './target-group-ref'; /** - * Properties for defining a listener rule + * Properties for defining a rule on a listener */ -export interface ListenerRuleProps { - /** - * The listener to attach the rule to - */ - listener: ListenerRef; - +export interface BaseListenerRuleProps { /** * Priority of the rule * @@ -49,6 +44,16 @@ export interface ListenerRuleProps { pathPattern?: string; } +/** + * Properties for defining a listener rule + */ +export interface ListenerRuleProps extends BaseListenerRuleProps { + /** + * The listener to attach the rule to + */ + listener: ListenerRef; +} + /** * Define a new listener rule */ @@ -65,6 +70,8 @@ export class ListenerRule extends cdk.Construct implements cdk.IDependable { private readonly conditions: {[key: string]: string[] | undefined} = {}; + private readonly actions: any[] = []; + constructor(parent: cdk.Construct, id: string, props: ListenerRuleProps) { super(parent, id); @@ -72,12 +79,7 @@ export class ListenerRule extends cdk.Construct implements cdk.IDependable { listenerArn: props.listener.listenerArn, priority: props.priority, conditions: new cdk.Token(() => this.renderConditions()), - actions: props.targets.map(target => ({ - targetGroupArn: target.targetGroupArn, - // The full spectrum of Actions is not supported via CloudFormation; - // only 'forward's currently. - type: 'forward' - })) + actions: new cdk.Token(() => this.renderActions()), }); if (props.hostHeader) { @@ -98,6 +100,17 @@ export class ListenerRule extends cdk.Construct implements cdk.IDependable { this.conditions[field] = values; } + /** + * Add a TargetGroup to load balance to + */ + public addTargetGroup(targetGroup: TargetGroupRef) { + this.actions.push({ + targetGroupArn: targetGroup.targetGroupArn, + type: 'forward' + }); + return targetGroup; + } + private renderConditions() { const ret = []; for (const [field, values] of Object.entries(this.conditions)) { @@ -107,4 +120,11 @@ export class ListenerRule extends cdk.Construct implements cdk.IDependable { } return ret; } + + private renderActions() { + if (this.actions.length === 0) { + throw new Error('Listener needs at least one default action'); + } + return this.actions; + } } diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/listener.ts index a0adc6d0eca29..0c01cf083531a 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/listener.ts @@ -1,3 +1,4 @@ +import ec2 = require('@aws-cdk/aws-ec2'); import cdk = require('@aws-cdk/cdk'); import { cloudformation, ListenerArn } from './elasticloadbalancingv2.generated'; import { ListenerRef } from './listener-ref'; @@ -7,14 +8,9 @@ import { Protocol } from './types'; import { determineProtocolAndPort } from './util'; /** - * Properties for defining a listener + * Properties for a listener on a load balancer */ -export interface ListenerProps { - /** - * The load balancer to attach this listener to - */ - loadBalancer: LoadBalancerRef; - +export interface BaseListenerProps { /** * The protocol to use * @@ -40,18 +36,25 @@ export interface ListenerProps { * @default the current predefined security policy. */ sslPolicy?: SslPolicy; +} +/** + * Properties for defining a listener + */ +export interface ListenerProps extends BaseListenerProps { /** - * Default target groups, which will be automatically 'forward'ed to. + * The load balancer to attach this listener to */ - defaultTargets: TargetGroupRef[]; + loadBalancer: LoadBalancerRef; } /** * Define a listener */ export class Listener extends ListenerRef { + public readonly connections: ec2.Connections; public readonly listenerArn: ListenerArn; + private readonly defaultActions: any[] = []; constructor(parent: cdk.Construct, id: string, props: ListenerProps) { super(parent, id); @@ -64,16 +67,37 @@ export class Listener extends ListenerRef { protocol, port, sslPolicy: props.sslPolicy, - defaultActions: props.defaultTargets.map(target => ({ - targetGroupArn: target.targetGroupArn, - // The full spectrum of Actions is not supported via CloudFormation; - // only 'forward's currently. - type: 'forward' - })) + defaultActions: new cdk.Token(() => this.renderActions()) + }); + + // This listener edits the securitygroup of the load balancer, + // but adds its own default port. + this.connections = new ec2.Connections({ + securityGroup: props.loadBalancer.connections.securityGroup, + defaultPortRange: new ec2.TcpPort(port), }); this.listenerArn = resource.ref; } + + /** + * Add a TargetGroup to load balance to + */ + public addDefaultTargetGroup(targetGroup: TargetGroupRef) { + this.defaultActions.push({ + targetGroupArn: targetGroup.targetGroupArn, + type: 'forward' + }); + this.registerTargetGroup(targetGroup); + return targetGroup; + } + + private renderActions() { + if (this.defaultActions.length === 0) { + throw new Error('Listener needs at least one default action'); + } + return this.defaultActions; + } } /** diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/load-balancer-ref.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/load-balancer-ref.ts index 835afaa73ffd7..a8da8201136e9 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/load-balancer-ref.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/load-balancer-ref.ts @@ -1,14 +1,16 @@ +import ec2 = require('@aws-cdk/aws-ec2'); import cdk = require('@aws-cdk/cdk'); import { LoadBalancerArn } from './elasticloadbalancingv2.generated'; +import { BaseListenerProps, Listener } from './listener'; /** * A load balancer */ -export abstract class LoadBalancerRef extends cdk.Construct { +export abstract class LoadBalancerRef extends cdk.Construct implements ec2.IConnectable { /** * Import an existing Load Balancer */ - public static import(parent: cdk.Construct, id: string, props: LoadBalancerRefProps) { + public static import(parent: cdk.Construct, id: string, props: LoadBalancerRefProps): LoadBalancerRef { return new ImportedLoadBalancer(parent, id, props); } @@ -17,6 +19,11 @@ export abstract class LoadBalancerRef extends cdk.Construct { */ public abstract readonly loadBalancerArn: LoadBalancerArn; + /** + * Connections for this load balancer + */ + public abstract readonly connections: ec2.Connections; + /** * Export this load balancer */ @@ -25,6 +32,16 @@ export abstract class LoadBalancerRef extends cdk.Construct { loadBalancerArn: new LoadBalancerArn(new cdk.Output(this, 'LoadBalancerArn', { value: this.loadBalancerArn }).makeImportValue()) }; } + + /** + * Add a listener to this load balancer + */ + public addListener(id: string, props: BaseListenerProps) { + return new Listener(this, id, { + loadBalancer: this, + ...props + }); + } } /** @@ -42,6 +59,8 @@ export interface LoadBalancerRefProps { */ class ImportedLoadBalancer extends LoadBalancerRef { public readonly loadBalancerArn: LoadBalancerArn; + public readonly connections: ec2.Connections = new ec2.ImportedConnections(); + constructor(parent: cdk.Construct, id: string, props: LoadBalancerRefProps) { super(parent, id); diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/load-balancer.ts index 3071661d5ee06..30e96dd9a84f8 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/load-balancer.ts @@ -173,7 +173,6 @@ export class LoadBalancer extends LoadBalancerRef implements ec2.IConnectable { public setAttribute(key: string, value: string | undefined) { this.attributes[key] = value; } - } /** @@ -231,4 +230,4 @@ const ELBV2_ACCOUNTS: {[region: string]: string } = { 'us-gov-west-1': '048591011584', 'cn-north-1': '638102146993', 'cn-northwest-1': '037604701340', -}; \ No newline at end of file +}; diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/target-group-ref.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/target-group-ref.ts index 7b9cabde80be2..1ed6f31d3f281 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/target-group-ref.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/target-group-ref.ts @@ -1,5 +1,7 @@ +import ec2 = require('@aws-cdk/aws-ec2'); import cdk = require('@aws-cdk/cdk'); import { TargetGroupArn } from './elasticloadbalancingv2.generated'; +import { IListenerInternals } from './listener-ref'; /** * A target group @@ -8,7 +10,7 @@ export abstract class TargetGroupRef extends cdk.Construct { /** * Import an existing target group */ - public static import(parent: cdk.Construct, id: string, props: TargetGroupRefProps) { + public static import(parent: cdk.Construct, id: string, props: TargetGroupRefProps): TargetGroupRef { return new ImportedTargetGroup(parent, id, props); } @@ -17,6 +19,9 @@ export abstract class TargetGroupRef extends cdk.Construct { */ public abstract readonly targetGroupArn: TargetGroupArn; + private readonly connectableMembers = new Array(); + private readonly listeners = new Array(); + /** * Export this target group */ @@ -25,6 +30,29 @@ export abstract class TargetGroupRef extends cdk.Construct { targetGroupArn: new TargetGroupArn(new cdk.Output(this, 'TargetGroupArn', { value: this.targetGroupArn }).makeImportValue()) }; } + + /** + * Register a connectable as a member of this target group + * + * The connections are created when the listener of a load balancer load + * balances to this target. + */ + public registerConnectable(connectable: ec2.IConnectable) { + this.connectableMembers.push(connectable); + for (const listener of this.listeners) { + listener.registerConnectable(connectable); + } + } + + /** + * Called when this TargetGroup is the target of a listener + */ + public bindToListener(listener: IListenerInternals): any { + for (const member of this.connectableMembers) { + listener.registerConnectable(member); + } + this.listeners.push(listener); + } } /** diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/target-group.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/target-group.ts index e8d3f960f9d0d..567610b0f70e0 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/target-group.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/target-group.ts @@ -1,6 +1,7 @@ import ec2 = require('@aws-cdk/aws-ec2'); import cdk = require('@aws-cdk/cdk'); import { cloudformation, TargetGroupArn, TargetGroupFullName, TargetGroupName } from './elasticloadbalancingv2.generated'; +import { TargetGroupRef } from './target-group-ref'; import { Protocol } from './types'; import { Attributes, determineProtocolAndPort, renderAttributes } from './util'; @@ -66,6 +67,15 @@ export interface TargetGroupProps { */ unhealthyThresholdCount?: number; + /** + * HTTP code to use when checking for a successful response from a target. + * + * For Application Load Balancers, you can specify values between 200 and + * 499, and the default value is 200. You can specify multiple values (for + * example, "200,202") or a range of values (for example, "200-299"). + */ + healthyHttpCodes?: string; + /** * The name of the target group. * @@ -96,36 +106,14 @@ export interface TargetGroupProps { */ protocol?: Protocol; - /** - * The type of target that you must specify when registering targets with this target group. - * - * The possible values are instance (targets are specified by instance ID) - * or ip (targets are specified by IP address). The default is instance. - * You can't specify targets for a target group using both instance IDs and - * IP addresses. - * - * If the target type is ip, specify IP addresses from the subnets of the - * virtual private cloud (VPC) for the target group, the RFC 1918 range - * (10.0.0.0/8, 172.16.0.0/12, and 192.168.0.0/16), and the RFC 6598 range - * (100.64.0.0/10). You can't specify publicly routable IP addresses. - * - * @default Instance - */ - targetType?: TargetType; - /** * The targets to add to this target group. - */ - targets?: TargetDescription[]; - - /** - * HTTP code to use when checking for a successful response from a target. * - * For Application Load Balancers, you can specify values between 200 and - * 499, and the default value is 200. You can specify multiple values (for - * example, "200,202") or a range of values (for example, "200-299"). + * Can be `Instance`, `IPAddress`, or any self-registering load balancing + * target. If you use either `Instance` or `IPAddress` as targets, all + * target must be of the same type. */ - healthyHttpCodes?: string; + targets?: ILoadBalancerTarget[]; /** * The virtual private cloud (VPC). @@ -142,7 +130,8 @@ export interface TargetGroupProps { deregistrationDelaySec?: number; /** - * The time period during which the load balancer sends a newly registered target a linearly increasing share of the traffic to the target group. + * The time period during which the load balancer sends a newly registered + * target a linearly increasing share of the traffic to the target group. * * The range is 30–900 seconds (15 minutes). * @@ -163,50 +152,17 @@ export interface TargetGroupProps { stickinessCookieDurationSec?: number; } -export interface TargetDescription { - /** - * An Availability Zone or all. - * - * This determines whether the target receives traffic from the load - * balancer nodes in the specified Availability Zone or from all enabled - * Availability Zones for the load balancer. - * - * This parameter is not supported if the target type of the target group - * is instance. If the IP address is in a subnet of the VPC for the target - * group, the Availability Zone is automatically detected and this - * parameter is optional. If the IP address is outside the VPC, this - * parameter is required. - * - * With an Application Load Balancer, if the IP address is outside the VPC - * for the target group, the only supported value is all. - * - * @default Automatic - */ - availabilityZone?: string; - - /** - * The ID of the target. - * - * If the target type of the target group is instance, specify an instance - * ID. If the target type is ip, specify an IP address. - */ - id: string; - - /** - * Override the default port on which the target is listening - */ - port?: number; -} - /** * Define the target of a load balancer */ -export class TargetGroup extends cdk.Construct { +export class TargetGroup extends TargetGroupRef { public readonly targetGroupArn: TargetGroupArn; public readonly targetGroupFullName: TargetGroupFullName; public readonly targetGroupName: TargetGroupName; private readonly attributes: Attributes = {}; + private readonly targetsJson = new Array(); + private targetType?: TargetType; constructor(parent: cdk.Construct, id: string, props: TargetGroupProps) { super(parent, id); @@ -230,8 +186,8 @@ export class TargetGroup extends cdk.Construct { protocol, port, targetGroupAttributes: new cdk.Token(() => renderAttributes(this.attributes)), - targetType: props.targetType, - targets: props.targets, + targetType: new cdk.Token(() => this.targetType), + targets: new cdk.Token(() => this.targetsJson), vpcId: props.vpc.vpcId, // HEALTH CHECK @@ -249,6 +205,8 @@ export class TargetGroup extends cdk.Construct { this.targetGroupArn = resource.ref; this.targetGroupFullName = resource.targetGroupFullName; this.targetGroupName = resource.targetGroupName; + + (props.targets || []).forEach(this.addTarget.bind(this)); } /** @@ -259,6 +217,26 @@ export class TargetGroup extends cdk.Construct { public setAttribute(key: string, value: string | undefined) { this.attributes[key] = value; } + + /** + * Register the given load balancing target as part of this group + */ + public addTarget(target: ILoadBalancerTarget) { + const ret = target.attachToELBv2TargetGroup(this); + if ((ret.targetType === TargetType.SelfRegistering) !== (ret.targetJson === undefined)) { + throw new Error('Load balancing target should specify targetJson if and only if TargetType is not SelfRegistering'); + } + if (ret.targetType !== TargetType.SelfRegistering) { + if (this.targetType !== undefined && this.targetType !== ret.targetType) { + throw new Error(`Already have a of type '${this.targetType}', adding '${ret.targetType}'; make all targets the same type.`); + } + this.targetType = ret.targetType; + } + + if (ret.targetJson) { + this.targetsJson.push(ret.targetJson); + } + } } /** @@ -273,5 +251,130 @@ export enum TargetType { /** * Targets identified by IP address */ - Ip = 'ip' + Ip = 'ip', + + /** + * A target that will register itself with the target group + */ + SelfRegistering = 'self-registering', +} + +/** + * Interface that is going to be implemented by constructs that you can load balance to + */ +export interface ILoadBalancerTarget { + /** + * Attach load-balanced target to a TargetGroup + * + * May return JSON to directly add to the [Targets] list, or return undefined + * if the target will register itself with the load balancer. + */ + attachToELBv2TargetGroup(targetGroup: TargetGroupRef): LoadBalancerTargetProps; +} + +/** + * Result of attaching a target to load balancer + */ +export interface LoadBalancerTargetProps { + /** + * What kind of target this is + */ + targetType: TargetType; + + /** + * JSON representing the target's direct addition to the TargetGroup list + */ + targetJson?: any; +} + +/** + * Properties for a load balancer target + */ +export interface TargetProps { + /** + * @default Automatic + */ + availabilityZone?: string; + + /** + * The ID of the target. + * + * If the target type of the target group is instance, specify an instance + * ID. If the target type is ip, specify an IP address. + */ + id: string; + + /** + * Override the default port on which the target is listening + */ + port?: number; +} + +/** + * An EC2 instance that is the target for load balancing + * + * If you register a target of this type, you are responsible for making + * sure the load balancer's security group can connect to the instance. + */ +export class Instance implements ILoadBalancerTarget { + /** + * Create a new Instance target + * + * @param instanceId Instance ID of the instance to register to + * @param port Override the default port for the target group + */ + constructor(private readonly instanceId: string, private readonly port?: number) { + } + + public attachToELBv2TargetGroup(_targetGroup: TargetGroupRef): LoadBalancerTargetProps { + return { + targetType: TargetType.Instance, + targetJson: { id: this.instanceId, port: this.port } + }; + } +} + +/** + * An IP address that is a target for load balancing. + * + * Specify IP addresses from the subnets of the virtual private cloud (VPC) for + * the target group, the RFC 1918 range (10.0.0.0/8, 172.16.0.0/12, and + * 192.168.0.0/16), and the RFC 6598 range (100.64.0.0/10). You can't specify + * publicly routable IP addresses. + * + * If you register a target of this type, you are responsible for making + * sure the load balancer's security group can send packets to the IP address. + */ +export class IPAddress implements ILoadBalancerTarget { + /** + * Create a new IPAddress target + * + * The availabilityZone parameter determines whether the target receives + * traffic from the load balancer nodes in the specified Availability Zone + * or from all enabled Availability Zones for the load balancer. + * + * This parameter is not supported if the target type of the target group + * is instance. If the IP address is in a subnet of the VPC for the target + * group, the Availability Zone is automatically detected and this + * parameter is optional. If the IP address is outside the VPC, this + * parameter is required. + * + * With an Application Load Balancer, if the IP address is outside the VPC + * for the target group, the only supported value is all. + * + * Default is automatic. + * + * @param ipAddress The IP Address to load balance to + * @param port Override the group's default port + * @param availabilityZone Availability zone to send traffic from + */ + constructor(private readonly ipAddress: string, private readonly port?: number, private readonly availabilityZone?: string) { + } + + public attachToELBv2TargetGroup(_targetGroup: TargetGroupRef): LoadBalancerTargetProps { + return { + targetType: TargetType.Ip, + targetJson: { id: this.ipAddress, port: this.port, availabilityZone: this.availabilityZone } + }; + } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/test.security-groups.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/test.security-groups.ts new file mode 100644 index 0000000000000..28f9c778eb333 --- /dev/null +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/test.security-groups.ts @@ -0,0 +1,17 @@ +import cdk = require('@aws-cdk/cdk'); +import { Test } from 'nodeunit'; + +export = { + 'bidirectional security groups are automatically added'(test: Test) { + // GIVEN + + + + + test.done(); + }, + + 'adding the same targets twice also works'(test: Test) { + test.done(); + }, +}; \ No newline at end of file From a25fee90d27b05b73684005346f2b7143c832456 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Wed, 19 Sep 2018 10:26:05 +0200 Subject: [PATCH 04/10] ALB, NLB, Unit Tests, document everything --- .../aws-autoscaling/lib/auto-scaling-group.ts | 15 +- .../test/integ.asg-w-elbv2.expected.json | 524 ++++++++++++++++++ .../aws-autoscaling/test/integ.asg-w-elbv2.ts | 7 +- .../aws-elasticloadbalancingv2/README.md | 173 +++++- .../aws-elasticloadbalancingv2/TODO.txt | 3 + .../application-listener-certificate.ts} | 14 +- .../application-listener-rule.ts} | 57 +- .../lib/alb/application-listener.ts | 461 +++++++++++++++ .../lib/alb/application-load-balancer.ts | 167 ++++++ .../lib/alb/application-target-group.ts | 197 +++++++ .../aws-elasticloadbalancingv2/lib/index.ts | 24 +- .../lib/listener-ref.ts | 96 ---- .../lib/listener.ts | 156 ------ .../lib/load-balancer-ref.ts | 69 --- .../lib/load-balancer.ts | 233 -------- .../lib/nlb/network-listener.ts | 172 ++++++ .../lib/nlb/network-load-balancer.ts | 86 +++ .../lib/nlb/network-target-group.ts | 91 +++ .../lib/shared/base-listener.ts | 61 ++ .../lib/shared/base-load-balancer.ts | 157 ++++++ .../lib/shared/base-target-group.ts | 295 ++++++++++ .../lib/shared/enums.ts | 117 ++++ .../lib/shared/imported.ts | 61 ++ .../lib/shared/load-balancer-targets.ts | 113 ++++ .../lib/shared/util.ts | 69 +++ .../lib/target-group-ref.ts | 78 --- .../lib/target-group.ts | 380 ------------- .../aws-elasticloadbalancingv2/lib/types.ts | 19 - .../aws-elasticloadbalancingv2/lib/util.ts | 49 -- .../aws-elasticloadbalancingv2/package.json | 3 +- .../test/alb/test.listener.ts | 305 ++++++++++ .../test/alb/test.load-balancer.ts | 127 +++++ .../test/alb/test.security-groups.ts | 135 +++++ .../test/helpers.ts | 26 + .../test/integ.alb.expected.json | 430 ++++++++++++++ .../test/integ.alb.ts | 36 ++ .../test/integ.nlb.expected.json | 365 ++++++++++++ .../test/integ.nlb.ts | 31 ++ .../test/nlb/test.listener.ts | 115 ++++ .../test/nlb/test.load-balancer.ts | 78 +++ .../test/test.elasticloadbalancingv2.ts | 8 - .../test/test.security-groups.ts | 17 - 42 files changed, 4468 insertions(+), 1152 deletions(-) create mode 100644 packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-elbv2.expected.json create mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/TODO.txt rename packages/@aws-cdk/aws-elasticloadbalancingv2/lib/{listener-certificate.ts => alb/application-listener-certificate.ts} (58%) rename packages/@aws-cdk/aws-elasticloadbalancingv2/lib/{listener-rule.ts => alb/application-listener-rule.ts} (66%) create mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts create mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts create mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts delete mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/lib/listener-ref.ts delete mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/lib/listener.ts delete mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/lib/load-balancer-ref.ts delete mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/lib/load-balancer.ts create mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener.ts create mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-load-balancer.ts create mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-target-group.ts create mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-listener.ts create mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts create mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts create mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/enums.ts create mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/imported.ts create mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/load-balancer-targets.ts create mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/util.ts delete mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/lib/target-group-ref.ts delete mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/lib/target-group.ts delete mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/lib/types.ts delete mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/lib/util.ts create mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.listener.ts create mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.load-balancer.ts create mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.security-groups.ts create mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/test/helpers.ts create mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.alb.expected.json create mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.alb.ts create mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.nlb.expected.json create mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.nlb.ts create mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/test.listener.ts create mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/test.load-balancer.ts delete mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/test/test.elasticloadbalancingv2.ts delete mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/test/test.security-groups.ts diff --git a/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts b/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts index 8bcf34b9b37ee..72722be4e79e9 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts @@ -137,7 +137,8 @@ export interface AutoScalingGroupProps { * * The ASG spans all availability zones. */ -export class AutoScalingGroup extends cdk.Construct implements elb.ILoadBalancerTarget, ec2.IConnectable, elbv2.ILoadBalancerTarget { +export class AutoScalingGroup extends cdk.Construct implements elb.ILoadBalancerTarget, ec2.IConnectable, + elbv2.IApplicationLoadBalancerTarget, elbv2.INetworkLoadBalancerTarget { /** * The type of OS instances of this fleet are running. */ @@ -252,14 +253,22 @@ export class AutoScalingGroup extends cdk.Construct implements elb.ILoadBalancer } /** - * Attach to ELBv2 Target Group + * Attach to ELBv2 Application Target Group */ - public attachToELBv2TargetGroup(targetGroup: elbv2.TargetGroupRef): elbv2.LoadBalancerTargetProps { + public attachToApplicationTargetGroup(targetGroup: elbv2.ApplicationTargetGroup): elbv2.LoadBalancerTargetProps { this.targetGroupArns.push(targetGroup.targetGroupArn); targetGroup.registerConnectable(this); return { targetType: elbv2.TargetType.SelfRegistering }; } + /** + * Attach to ELBv2 Application Target Group + */ + public attachToNetworkTargetGroup(targetGroup: elbv2.NetworkTargetGroup): elbv2.LoadBalancerTargetProps { + this.targetGroupArns.push(targetGroup.targetGroupArn); + return { targetType: elbv2.TargetType.SelfRegistering }; + } + /** * Add command to the startup script of fleet instances. * The command must be in the scripting language supported by the fleet's OS (i.e. Linux/Windows). diff --git a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-elbv2.expected.json b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-elbv2.expected.json new file mode 100644 index 0000000000000..7e8084da884f9 --- /dev/null +++ b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-elbv2.expected.json @@ -0,0 +1,524 @@ +{ + "Resources": { + "VPCB9E5F0B4": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-integ/VPC" + } + ] + } + }, + "VPCPublicSubnet1SubnetB4246D30": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/18", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-integ/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1RouteTableFEE4B781": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-integ/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1RouteTableAssociatioin249B4093": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + } + } + }, + "VPCPublicSubnet1EIP6AD938E8": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc" + } + }, + "VPCPublicSubnet1NATGatewayE0556630": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet1EIP6AD938E8", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-integ/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1DefaultRoute91CEF279": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, + "VPCPublicSubnet2Subnet74179F39": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.64.0/18", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-integ/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet2RouteTable6F1A15F1": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-integ/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet2RouteTableAssociatioin766225D7": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + } + } + }, + "VPCPublicSubnet2EIP4947BC00": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc" + } + }, + "VPCPublicSubnet2NATGateway3C070193": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet2EIP4947BC00", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-integ/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet2DefaultRouteB7481BBA": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, + "VPCPrivateSubnet1Subnet8BCA10E0": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/18", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-integ/VPC/PrivateSubnet1" + } + ] + } + }, + "VPCPrivateSubnet1RouteTableBE8A6027": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-integ/VPC/PrivateSubnet1" + } + ] + } + }, + "VPCPrivateSubnet1RouteTableAssociatioin77F7CA18": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + } + } + }, + "VPCPrivateSubnet1DefaultRouteAE1D6490": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet1NATGatewayE0556630" + } + } + }, + "VPCPrivateSubnet2SubnetCFCDAA7A": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.192.0/18", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-integ/VPC/PrivateSubnet2" + } + ] + } + }, + "VPCPrivateSubnet2RouteTable0A19E10E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-integ/VPC/PrivateSubnet2" + } + ] + } + }, + "VPCPrivateSubnet2RouteTableAssociatioinC31995B4": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + } + } + }, + "VPCPrivateSubnet2DefaultRouteF4F5CFD2": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet2NATGateway3C070193" + } + } + }, + "VPCIGWB7E252D3": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-integ/VPC" + } + ] + } + }, + "VPCVPCGW99B986DC": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "InternetGatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, + "FleetInstanceSecurityGroupA8C3D7AD": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "aws-cdk-ec2-integ/Fleet/InstanceSecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Outbound traffic allowed by default", + "FromPort": -1, + "IpProtocol": "-1", + "ToPort": -1 + } + ], + "SecurityGroupIngress": [], + "VpcId": { + "Ref": "VPCB9E5F0B4" + } + } + }, + "FleetInstanceSecurityGroupfromawscdkec2integLBSecurityGroupDEF4F99A8025E910CB": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "tcp", + "Description": "Load balancer to target", + "FromPort": 80, + "GroupId": { + "Fn::GetAtt": [ + "FleetInstanceSecurityGroupA8C3D7AD", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "LBSecurityGroup8A41EA2B", + "GroupId" + ] + }, + "ToPort": 80 + } + }, + "FleetInstanceRoleA605DB82": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ec2.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "FleetInstanceProfileC6192A66": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": { + "Roles": [ + { + "Ref": "FleetInstanceRoleA605DB82" + } + ] + } + }, + "FleetLaunchConfig59F79D36": { + "Type": "AWS::AutoScaling::LaunchConfiguration", + "Properties": { + "ImageId": "ami-1234", + "InstanceType": "t2.micro", + "IamInstanceProfile": { + "Ref": "FleetInstanceProfileC6192A66" + }, + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "FleetInstanceSecurityGroupA8C3D7AD", + "GroupId" + ] + } + ], + "UserData": { + "Fn::Base64": "#!/bin/bash\n" + } + }, + "DependsOn": [ + "FleetInstanceRoleA605DB82" + ] + }, + "FleetASG3971DFE5": { + "Type": "AWS::AutoScaling::AutoScalingGroup", + "Properties": { + "MaxSize": "1", + "MinSize": "1", + "DesiredCapacity": "1", + "LaunchConfigurationName": { + "Ref": "FleetLaunchConfig59F79D36" + }, + "TargetGroupARNs": [ + { + "Ref": "LBListenerTargetGroupF04FCF6D" + } + ], + "VPCZoneIdentifier": [ + { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + }, + { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + } + ] + }, + "UpdatePolicy": { + "AutoScalingScheduledAction": { + "IgnoreUnmodifiedGroupSizeProperties": true + } + } + }, + "LB8A12904C": { + "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", + "Properties": { + "LoadBalancerAttributes": [], + "Scheme": "internet-facing", + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "LBSecurityGroup8A41EA2B", + "GroupId" + ] + } + ], + "Subnets": [ + { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, + { + "Ref": "VPCPublicSubnet2Subnet74179F39" + } + ], + "Type": "application" + } + }, + "LBSecurityGroup8A41EA2B": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "Automatically created Security Group for ELB awscdkec2integLB366431B5", + "SecurityGroupEgress": [], + "SecurityGroupIngress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Open to the world", + "FromPort": 80, + "IpProtocol": "tcp", + "ToPort": 80 + } + ], + "VpcId": { + "Ref": "VPCB9E5F0B4" + } + } + }, + "LBSecurityGrouptoawscdkec2integFleetInstanceSecurityGroupB03BE84D80B371C596": { + "Type": "AWS::EC2::SecurityGroupEgress", + "Properties": { + "GroupId": { + "Fn::GetAtt": [ + "LBSecurityGroup8A41EA2B", + "GroupId" + ] + }, + "IpProtocol": "tcp", + "Description": "Load balancer to target", + "DestinationSecurityGroupId": { + "Fn::GetAtt": [ + "FleetInstanceSecurityGroupA8C3D7AD", + "GroupId" + ] + }, + "FromPort": 80, + "ToPort": 80 + } + }, + "LBListener49E825B4": { + "Type": "AWS::ElasticLoadBalancingV2::Listener", + "Properties": { + "DefaultActions": [ + { + "TargetGroupArn": { + "Ref": "LBListenerTargetGroupF04FCF6D" + }, + "Type": "forward" + } + ], + "LoadBalancerArn": { + "Ref": "LB8A12904C" + }, + "Port": 80, + "Protocol": "HTTP", + "Certificates": [] + } + }, + "LBListenerTargetGroupF04FCF6D": { + "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", + "Properties": { + "Port": 80, + "Protocol": "HTTP", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "TargetGroupAttributes": [], + "Targets": [] + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-elbv2.ts b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-elbv2.ts index 20fda8f06d4a3..8f2d2d657b7f4 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-elbv2.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-elbv2.ts @@ -17,7 +17,7 @@ const asg = new autoscaling.AutoScalingGroup(stack, 'Fleet', { machineImage: new ec2.AmazonLinuxImage(), }); -const lb = new elbv2.LoadBalancer(stack, 'LB', { +const lb = new elbv2.ApplicationLoadBalancer(stack, 'LB', { vpc, internetFacing: true }); @@ -26,11 +26,10 @@ const listener = lb.addListener('Listener', { port: 80, }); -listener.addDefaultTargetGroup(new elbv2.TargetGroup(stack, 'Target', { - vpc, +listener.addTargets('Target', { port: 80, targets: [asg] -})); +}); listener.connections.allowDefaultPortFromAnyIpv4('Open to the world'); diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/README.md b/packages/@aws-cdk/aws-elasticloadbalancingv2/README.md index 236ac2628a36c..24c2d1c3580ed 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/README.md +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/README.md @@ -1,2 +1,171 @@ -## The CDK Construct Library for AWS Elastic Load Balancing (V2) -This module is part of the [AWS Cloud Development Kit](https://github.com/awslabs/aws-cdk) project. +## AWS Application and Network Load Balancing Construct Library + +The `@aws-cdk/aws-elasticloadbalancingv2` package provides constructs for +configuring application and network load balancers. + +### Defining an Application Load Balancer + +You define an application load balancer by creating an instance of +`ApplicationLoadBalancer`, adding a Listener to the load balancer +and adding Targets to the Listener: + +```ts +import ec2 = require('@aws-cdk/aws-ec2'); +import elbv2 = require('@aws-cdk/aws-elasticloadbalancingv2'); +import autoscaling = require('@aws-cdk/aws-autoscaling'); + +// ... + +const vpc = new ec2.VpcNetwork(...); + +// Create the load balancer in a VPC. Set 'internetFacing' to 'false' to +// create an internal load balancer. +const lb = new elbv2.ApplicationLoadBalancer(this, 'LB', { + vpc, + internetFacing: true +}); + +// Add a listener and open up the load balancer's security group +// to the world. +const listener = lb.addListener('Listener', { + port: 80, +}); +listener.connections.allowDefaultPortFromAnyIpv4('Open to the world'); + +// Create an AutoScaling group and add it as a load balancing +// target to the listener. +const asg = new autoscaling.AutoScalingGroup(...); +listener.addTargets('Targets', { + port: 8080, + targets: [asg] +}); +``` + +The security groups of the load balancer and the target are automatically +updated to allow the network traffic. + +#### Conditions + +It's possible to route traffic to targets based on conditions in the incoming +HTTP request. Path- and host-based conditions are supported. For example, +the following will route requests to the indicated AutoScalingGroup group +only if the requested host in the request is `example.com`: + +```ts +listener.addTargets('Targets', { + priority: 10, + hostHeader: 'example.com', + port: 8080, + targets: [asg] +}); +``` + +`priority` is a required field when you add targets with conditions. The lowest +number wins. + +Every listener must have at least one target without conditions. + +### Defining a Network Load Balancer + +Network Load Balancers are defined in a similar way to Application Load +Balancers: + +```ts +import ec2 = require('@aws-cdk/aws-ec2'); +import elbv2 = require('@aws-cdk/aws-elasticloadbalancingv2'); +import autoscaling = require('@aws-cdk/aws-autoscaling'); + +// Create the load balancer in a VPC. Set 'internetFacing' to 'false' to +// create an internal load balancer. +const lb = new elbv2.NetworkLoadBalancer(stack, 'LB', { + vpc, + internetFacing: true +}); + +// Add a listener on a particular port. +const listener = lb.addListener('Listener', { + port: 443, +}); + +// Add targets on a particular port. +listener.addTargets('Target', { + port: 443, + targets: [asg] +}); +``` + +One thing to keep in mind is that network load balancers do not have security +groups, and no automatic security group configuration is done for you. You will +have to configure the security groups of the target yourself to allow traffic by +clients and/or load balancer instances, depending on your target types. See +[Target Groups for your Network Load +Balancers](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/load-balancer-target-groups.html) +and [Register targets with your Target +Group](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/target-group-register-targets.html) +for more information. + +### Targets and Target Groups + +Application and Network Load Balancers organize load balancing targets in Target +Groups. If you add your balancing targets (such as AutoScaling groups, ECS +services or individual instances) to your listener directly, the appropriate +`TargetGroup` will be automatically created for you. + +If you need more control over the Target Groups created, create an instance of +`ApplicationTargetGroup` or `NetworkTargetGroup`, add the members you desire, +and add it to the listener by calling `addTargetGroups` instead of `addTargets`. + +`addTargets()` will always return the Target Group it just created for you. + +### Configuring Health Checks + +Health checks are configured upon creation of a target group: + +```ts +listener.addTargets('Targets', { + port: 8080, + targets: [asg], + healthCheck: { + path: '/ping', + intervalSecs: 60, + } +}); +``` + +The health check can also be configured after creation by calling +`configureHealthCheck()` on the created object. + +No attempts are made to configure security groups for the port you're +configuring a health check for, but if the health check is on the same port +you're routing traffic to, the security group already allows the traffic. +If not, you will have to configure the security groups appropriately. + +### Protocol for Load Balancer Targets + +Constructs that want to be a load balancer target should implement +`IApplicationLoadBalancerTarget` and/or `INetworkLoadBalancerTarget`, and +provide an implementation for the function `attachToXxxTargetGroup()`, which can +call functions on the load balancer and should return metadata about the +load balancing target: + +```ts +public attachToApplicationTargetGroup(targetGroup: ApplicationTargetGroup): LoadBalancerTargetProps { + targetGroup.registerConnectable(...); + return { + targetType: TargetType.Instance | TargetType.Ip | TargetType.SelfRegistering, + targetJson: { id: ..., port: ... }, + }; +} +``` + +`targetType` should be one of `Instance` or `Ip` if the target can be directly +added to the target group, or `SelfRegistering` if the target will register new +instances with the load balancer at some later point. + +If the `targetType` is `Instance` or `Ip`, `targetJson` should contain the `id` +of the target (either instance ID or IP address depending on the type) and +optionally a `port` or `availabilityZone` override. + +Application load balancer targets can call `registerConnectable()` on the +target group to register themselves for addition to the load balancer's security +group rules. diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/TODO.txt b/packages/@aws-cdk/aws-elasticloadbalancingv2/TODO.txt new file mode 100644 index 0000000000000..ffb92ac9dd24f --- /dev/null +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/TODO.txt @@ -0,0 +1,3 @@ +- Integ Tests +- README +- Docstrings everywhere diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/listener-certificate.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-certificate.ts similarity index 58% rename from packages/@aws-cdk/aws-elasticloadbalancingv2/lib/listener-certificate.ts rename to packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-certificate.ts index bbd47a8c40ba3..327f8f2a420ac 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/listener-certificate.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-certificate.ts @@ -1,15 +1,15 @@ import cdk = require('@aws-cdk/cdk'); -import { cloudformation } from './elasticloadbalancingv2.generated'; -import { ListenerRef } from './listener-ref'; +import { cloudformation } from '../elasticloadbalancingv2.generated'; +import { IApplicationListener } from './application-listener'; /** * Properties for adding a set of certificates to a listener */ -export interface ListenerCertificateProps { +export interface ApplicationListenerCertificateProps { /** * The listener to attach the rule to */ - listener: ListenerRef; + listener: IApplicationListener; /** * ARNs of certificates to attach @@ -22,18 +22,18 @@ export interface ListenerCertificateProps { /** * Add certificates to a listener */ -export class ListenerCertificate extends cdk.Construct implements cdk.IDependable { +export class ApplicationListenerCertificate extends cdk.Construct implements cdk.IDependable { /** * The elements of this resou rce to add ordering dependencies on */ public readonly dependencyElements: cdk.IDependable[] = []; - constructor(parent: cdk.Construct, id: string, props: ListenerCertificateProps) { + constructor(parent: cdk.Construct, id: string, props: ApplicationListenerCertificateProps) { super(parent, id); const resource = new cloudformation.ListenerCertificateResource(this, 'Resource', { listenerArn: props.listener.listenerArn, - certificates: props.certificateArns, + certificates: props.certificateArns.map(certificateArn => ({ certificateArn })), }); this.dependencyElements.push(resource); diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/listener-rule.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts similarity index 66% rename from packages/@aws-cdk/aws-elasticloadbalancingv2/lib/listener-rule.ts rename to packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts index 19e3914d6b9fe..6b9dab6ce7679 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/listener-rule.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts @@ -1,12 +1,12 @@ import cdk = require('@aws-cdk/cdk'); -import { cloudformation, ListenerRuleArn } from './elasticloadbalancingv2.generated'; -import { ListenerRef } from './listener-ref'; -import { TargetGroupRef } from './target-group-ref'; +import { cloudformation, ListenerRuleArn } from '../elasticloadbalancingv2.generated'; +import { IApplicationListener } from './application-listener'; +import { IApplicationTargetGroup } from './application-target-group'; /** - * Properties for defining a rule on a listener + * Basic properties for defining a rule on a listener */ -export interface BaseListenerRuleProps { +export interface BaseApplicationListenerRuleProps { /** * Priority of the rule * @@ -19,7 +19,7 @@ export interface BaseListenerRuleProps { /** * Target groups to forward requests to */ - targets: TargetGroupRef[]; + targetGroups?: IApplicationTargetGroup[]; /** * Rule applies if the requested host matches the indicated host @@ -47,17 +47,17 @@ export interface BaseListenerRuleProps { /** * Properties for defining a listener rule */ -export interface ListenerRuleProps extends BaseListenerRuleProps { +export interface ApplicationListenerRuleProps extends BaseApplicationListenerRuleProps { /** * The listener to attach the rule to */ - listener: ListenerRef; + listener: IApplicationListener; } /** * Define a new listener rule */ -export class ListenerRule extends cdk.Construct implements cdk.IDependable { +export class ApplicationListenerRule extends cdk.Construct implements cdk.IDependable { /** * The ARN of this rule */ @@ -71,15 +71,22 @@ export class ListenerRule extends cdk.Construct implements cdk.IDependable { private readonly conditions: {[key: string]: string[] | undefined} = {}; private readonly actions: any[] = []; + private readonly listener: IApplicationListener; - constructor(parent: cdk.Construct, id: string, props: ListenerRuleProps) { + constructor(parent: cdk.Construct, id: string, props: ApplicationListenerRuleProps) { super(parent, id); + if (!props.hostHeader && !props.pathPattern) { + throw new Error(`At least one of 'hostHeader' or 'pathPattern' is required when defining a load balancing rule.`); + } + + this.listener = props.listener; + const resource = new cloudformation.ListenerRuleResource(this, 'Resource', { listenerArn: props.listener.listenerArn, priority: props.priority, conditions: new cdk.Token(() => this.renderConditions()), - actions: new cdk.Token(() => this.renderActions()), + actions: new cdk.Token(() => this.actions), }); if (props.hostHeader) { @@ -89,6 +96,8 @@ export class ListenerRule extends cdk.Construct implements cdk.IDependable { this.setCondition('path-pattern', [props.pathPattern]); } + (props.targetGroups || []).forEach(this.addTargetGroup.bind(this)); + this.dependencyElements.push(resource); this.listenerRuleArn = resource.ref; } @@ -100,17 +109,30 @@ export class ListenerRule extends cdk.Construct implements cdk.IDependable { this.conditions[field] = values; } + /** + * Validate the rule + */ + public validate() { + if (this.actions.length === 0) { + return ['Listener rule needs at least one action']; + } + return []; + } + /** * Add a TargetGroup to load balance to */ - public addTargetGroup(targetGroup: TargetGroupRef) { + public addTargetGroup(targetGroup: IApplicationTargetGroup) { this.actions.push({ targetGroupArn: targetGroup.targetGroupArn, type: 'forward' }); - return targetGroup; + targetGroup.registerListener(this.listener); } + /** + * Render the conditions for this rule + */ private renderConditions() { const ret = []; for (const [field, values] of Object.entries(this.conditions)) { @@ -120,11 +142,4 @@ export class ListenerRule extends cdk.Construct implements cdk.IDependable { } return ret; } - - private renderActions() { - if (this.actions.length === 0) { - throw new Error('Listener needs at least one default action'); - } - return this.actions; - } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts new file mode 100644 index 0000000000000..2cd2522c4cf37 --- /dev/null +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts @@ -0,0 +1,461 @@ +import ec2 = require('@aws-cdk/aws-ec2'); +import cdk = require('@aws-cdk/cdk'); +import { ListenerArn } from '../elasticloadbalancingv2.generated'; +import { BaseListener, ListenerRefProps } from '../shared/base-listener'; +import { HealthCheck } from '../shared/base-target-group'; +import { ApplicationProtocol, SslPolicy } from '../shared/enums'; +import { BaseImportedListener } from '../shared/imported'; +import { determineProtocolAndPort } from '../shared/util'; +import { ApplicationListenerCertificate } from './application-listener-certificate'; +import { ApplicationListenerRule } from './application-listener-rule'; +import { IApplicationLoadBalancer } from './application-load-balancer'; +import { ApplicationTargetGroup, IApplicationLoadBalancerTarget, IApplicationTargetGroup } from './application-target-group'; + +/** + * Basic properties for an ApplicationListener + */ +export interface BaseApplicationListenerProps { + /** + * The protocol to use + * + * @default Determined from port if known + */ + protocol?: ApplicationProtocol; + + /** + * The port on which the listener listens for requests. + * + * @default Determined from protocol if known + */ + port?: number; + + /** + * The certificates to use on this listener + */ + certificateArns?: cdk.Arn[]; + + /** + * The security policy that defines which ciphers and protocols are supported. + * + * @default the current predefined security policy. + */ + sslPolicy?: SslPolicy; + + /** + * Default target groups to load balance to + * + * @default None + */ + defaultTargetGroups?: IApplicationTargetGroup[]; +} + +/** + * Properties for defining a standalone ApplicationListener + */ +export interface ApplicationListenerProps extends BaseApplicationListenerProps { + /** + * The load balancer to attach this listener to + */ + loadBalancer: IApplicationLoadBalancer; +} + +/** + * Define an ApplicationListener + */ +export class ApplicationListener extends BaseListener implements IApplicationListener { + /** + * Import an existing listener + */ + public static import(parent: cdk.Construct, id: string, props: ListenerRefProps): IApplicationListener { + return new ImportedApplicationListener(parent, id, props); + } + + /** + * Manage connections to this ApplicationListener + */ + public readonly connections: ec2.Connections; + + /** + * ARNs of certificates added to this listener + */ + private readonly certificateArns: cdk.Arn[]; + + /** + * Load balancer this listener is associated with + */ + private readonly loadBalancer: IApplicationLoadBalancer; + + /** + * Listener protocol for this listener. + */ + private readonly protocol: ApplicationProtocol; + + constructor(parent: cdk.Construct, id: string, props: ApplicationListenerProps) { + const [protocol, port] = determineProtocolAndPort(props.protocol, props.port); + + super(parent, id, { + loadBalancerArn: props.loadBalancer.loadBalancerArn, + certificates: new cdk.Token(() => this.certificateArns.map(certificateArn => ({ certificateArn }))), + protocol, + port, + sslPolicy: props.sslPolicy, + }); + + this.loadBalancer = props.loadBalancer; + this.protocol = protocol; + this.certificateArns = []; + this.certificateArns.push(...(props.certificateArns || [])); + + // This listener edits the securitygroup of the load balancer, + // but adds its own default port. + this.connections = new ec2.Connections({ + securityGroup: props.loadBalancer.connections.securityGroup, + defaultPortRange: new ec2.TcpPort(port), + }); + + (props.defaultTargetGroups || []).forEach(this.addDefaultTargetGroup.bind(this)); + } + + /** + * Add one or more certificates to this listener. + */ + public addCertificateArns(_id: string, ...arns: cdk.Arn[]): void { + this.certificateArns.push(...arns); + } + + /** + * Load balance incoming requests to the given target groups. + * + * It's possible to add conditions to the TargetGroups added in this way. + * At least one TargetGroup must be added without conditions. + */ + public addTargetGroups(id: string, props: AddApplicationTargetGroupsProps): void { + if ((props.hostHeader !== undefined || props.pathPattern !== undefined) !== (props.priority !== undefined)) { + throw new Error(`Setting 'pathPattern' or 'hostHeader' also requires 'priority', and vice versa`); + } + + if (props.priority !== undefined) { + // New rule + // + // TargetGroup.registerListener is called inside ApplicationListenerRule. + new ApplicationListenerRule(this, id + 'Rule', { + listener: this, + hostHeader: props.hostHeader, + pathPattern: props.pathPattern, + priority: props.priority, + targetGroups: props.targetGroups + }); + } else { + // New default target(s) + for (const targetGroup of props.targetGroups) { + this.addDefaultTargetGroup(targetGroup); + } + } + } + + /** + * Load balance incoming requests to the given load balancing targets. + * + * This method implicitly creates an ApplicationTargetGroup for the targets + * involved. + * + * It's possible to add conditions to the targets added in this way. At least + * one set of targets must be added without conditions. + * + * @returns The newly created target group + */ + public addTargets(id: string, props: AddApplicationTargetsProps): ApplicationTargetGroup { + if (!this.loadBalancer.vpc) { + // tslint:disable-next-line:max-line-length + throw new Error('Can only call addTargets() when using a constructed Load Balancer; construct a new TargetGroup and use addTargetGroup'); + } + + const group = new ApplicationTargetGroup(this, id + 'Group', { + deregistrationDelaySec: props.deregistrationDelaySec, + healthCheck: props.healthCheck, + port: props.port, + protocol: props.protocol, + slowStartSec: props.slowStartSec, + stickinessCookieDurationSec: props.stickinessCookieDurationSec, + targetGroupName: props.targetGroupName, + targets: props.targets, + vpc: this.loadBalancer.vpc, + }); + + this.addTargetGroups(id, { + hostHeader: props.hostHeader, + pathPattern: props.pathPattern, + priority: props.priority, + targetGroups: [group], + }); + + return group; + } + + /** + * Register that a connectable that has been added to this load balancer. + * + * Don't call this directly. It is called by ApplicationTargetGroup. + */ + public registerConnectable(connectable: ec2.IConnectable, portRange: ec2.IPortRange): void { + this.connections.allowTo(connectable, portRange, 'Load balancer to target'); + } + + /** + * Validate this listener. + */ + public validate(): string[] { + const errors = super.validate(); + if (this.protocol === ApplicationProtocol.Https && this.certificateArns.length === 0) { + errors.push('HTTPS Listener needs at least one certificate (call addCertificateArns)'); + } + return errors; + } + + /** + * Add a default TargetGroup + */ + private addDefaultTargetGroup(targetGroup: IApplicationTargetGroup) { + this._addDefaultTargetGroup(targetGroup); + targetGroup.registerListener(this); + } +} + +/** + * Properties to reference an existing listener + */ +export interface IApplicationListener extends ec2.IConnectable { + /** + * ARN of the listener + */ + listenerArn: ListenerArn; + + /** + * Add one or more certificates to this listener. + */ + addCertificateArns(id: string, ...arns: cdk.Arn[]): void; + + /** + * Load balance incoming requests to the given target groups. + * + * It's possible to add conditions to the TargetGroups added in this way. + * At least one TargetGroup must be added without conditions. + */ + addTargetGroups(id: string, props: AddApplicationTargetGroupsProps): void; + + /** + * Load balance incoming requests to the given load balancing targets. + * + * This method implicitly creates an ApplicationTargetGroup for the targets + * involved. + * + * It's possible to add conditions to the targets added in this way. At least + * one set of targets must be added without conditions. + * + * @returns The newly created target group + */ + addTargets(id: string, props: AddApplicationTargetsProps): ApplicationTargetGroup; + + /** + * Register that a connectable that has been added to this load balancer. + * + * Don't call this directly. It is called by ApplicationTargetGroup. + */ + registerConnectable(connectable: ec2.IConnectable, portRange: ec2.IPortRange): void; +} + +class ImportedApplicationListener extends BaseImportedListener implements IApplicationListener { + // FIXME: Proper security group import? + public readonly connections: ec2.Connections = new ec2.ImportedConnections(); + + /** + * Add one or more certificates to this listener. + */ + public addCertificateArns(id: string, ...arns: cdk.Arn[]): void { + new ApplicationListenerCertificate(this, id, { + listener: this, + certificateArns: arns + }); + } + + /** + * Load balance incoming requests to the given target groups. + * + * It's possible to add conditions to the TargetGroups added in this way. + * At least one TargetGroup must be added without conditions. + */ + public addTargetGroups(id: string, props: AddApplicationTargetGroupsProps): void { + if ((props.hostHeader !== undefined || props.pathPattern !== undefined) !== (props.priority !== undefined)) { + throw new Error(`Setting 'pathPattern' or 'hostHeader' also requires 'priority', and vice versa`); + } + + if (props.priority !== undefined) { + // New rule + new ApplicationListenerRule(this, id, { + listener: this, + hostHeader: props.hostHeader, + pathPattern: props.pathPattern, + priority: props.priority, + targetGroups: props.targetGroups + }); + } else { + throw new Error('Cannot add default Target Groups to imported ApplicationListener'); + } + } + + /** + * Load balance incoming requests to the given load balancing targets. + * + * This method implicitly creates an ApplicationTargetGroup for the targets + * involved. + * + * It's possible to add conditions to the targets added in this way. At least + * one set of targets must be added without conditions. + * + * @returns The newly created target group + */ + public addTargets(_id: string, _props: AddApplicationTargetsProps): ApplicationTargetGroup { + // tslint:disable-next-line:max-line-length + throw new Error('Can only call addTargets() when using a constructed ApplicationListener; construct a new TargetGroup and use addTargetGroup.'); + } + + /** + * Register that a connectable that has been added to this load balancer. + * + * Don't call this directly. It is called by ApplicationTargetGroup. + */ + public registerConnectable(connectable: ec2.IConnectable, portRange: ec2.IPortRange): void { + this.connections.allowTo(connectable, portRange, 'Load balancer to target'); + } +} + +/** + * Properties for adding a conditional load balancing rule + */ +export interface AddRuleProps { + /** + * Priority of this target group + * + * The rule with the lowest priority will be used for every request. + * If priority is not given, these target groups will be added as + * defaults, and must not have conditions. + * + * Priorities must be unique. + * + * @default Target groups are used as defaults + */ + priority?: number; + + /** + * Rule applies if the requested host matches the indicated host + * + * May contain up to three '*' wildcards. + * + * Requires that priority is set. + * + * @see https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-listeners.html#host-conditions + * + * @default No host condition + */ + hostHeader?: string; + + /** + * Rule applies if the requested path matches the given path pattern + * + * May contain up to three '*' wildcards. + * + * Requires that priority is set. + * + * @see https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-listeners.html#path-conditions + * + * @default No path condition + */ + pathPattern?: string; +} + +/** + * Properties for adding a new target group to a listener + */ +export interface AddApplicationTargetGroupsProps extends AddRuleProps { + /** + * Target groups to forward requests to + */ + targetGroups: IApplicationTargetGroup[]; +} + +/** + * Properties for adding new targets to a listener + */ +export interface AddApplicationTargetsProps extends AddRuleProps { + /** + * The protocol to use + * + * @default Determined from port if known + */ + protocol?: ApplicationProtocol; + + /** + * The port on which the listener listens for requests. + * + * @default Determined from protocol if known + */ + port?: number; + + /** + * The time period during which the load balancer sends a newly registered + * target a linearly increasing share of the traffic to the target group. + * + * The range is 30–900 seconds (15 minutes). + * + * @default 0 + */ + slowStartSec?: number; + + /** + * The stickiness cookie expiration period. + * + * Setting this value enables load balancer stickiness. + * + * After this period, the cookie is considered stale. The minimum value is + * 1 second and the maximum value is 7 days (604800 seconds). + * + * @default 86400 (1 day) + */ + stickinessCookieDurationSec?: number; + + /** + * The targets to add to this target group. + * + * Can be `Instance`, `IPAddress`, or any self-registering load balancing + * target. If you use either `Instance` or `IPAddress` as targets, all + * target must be of the same type. + */ + targets?: IApplicationLoadBalancerTarget[]; + + /** + * The name of the target group. + * + * This name must be unique per region per account, can have a maximum of + * 32 characters, must contain only alphanumeric characters or hyphens, and + * must not begin or end with a hyphen. + * + * @default Automatically generated + */ + targetGroupName?: string; + + /** + * The amount of time for Elastic Load Balancing to wait before deregistering a target. + * + * The range is 0–3600 seconds. + * + * @default 300 + */ + deregistrationDelaySec?: number; + + /** + * Health check configuration + * + * @default No health check + */ + healthCheck?: HealthCheck; +} diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts new file mode 100644 index 0000000000000..759574cb58f3d --- /dev/null +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts @@ -0,0 +1,167 @@ +import ec2 = require('@aws-cdk/aws-ec2'); +import s3 = require('@aws-cdk/aws-s3'); +import cdk = require('@aws-cdk/cdk'); +import { LoadBalancerArn } from '../elasticloadbalancingv2.generated'; +import { BaseLoadBalancer, BaseLoadBalancerProps, LoadBalancerRefProps } from '../shared/base-load-balancer'; +import { IpAddressType } from '../shared/enums'; +import { BaseImportedLoadBalancer } from '../shared/imported'; +import { ApplicationListener, BaseApplicationListenerProps } from './application-listener'; + +/** + * Properties for defining an Application Load Balancer + */ +export interface ApplicationLoadBalancerProps extends BaseLoadBalancerProps { + /** + * Security group to associate with this load balancer + * + * @default A security group is created + */ + securityGroup?: ec2.SecurityGroupRef; + + /** + * The type of IP addresses to use + * + * Only applies to application load balancers. + * + * @default IpAddressType.Ipv4 + */ + ipAddressType?: IpAddressType; + + /** + * Indicates whether HTTP/2 is enabled. + * + * @default true + */ + http2Enabled?: boolean; + + /** + * The load balancer idle timeout, in seconds + * + * @default 60 + */ + idleTimeoutSecs?: number; +} + +/** + * Define an Application Load Balancer + */ +export class ApplicationLoadBalancer extends BaseLoadBalancer implements IApplicationLoadBalancer { + /** + * Import an existing Application Load Balancer + */ + public static import(parent: cdk.Construct, id: string, props: LoadBalancerRefProps): IApplicationLoadBalancer { + return new ImportedApplicationLoadBalancer(parent, id, props); + } + + public readonly connections: ec2.Connections; + private readonly securityGroup: ec2.SecurityGroupRef; + + constructor(parent: cdk.Construct, id: string, props: ApplicationLoadBalancerProps) { + super(parent, id, props, { + type: "application", + securityGroups: new cdk.Token(() => [this.securityGroup.securityGroupId]), + ipAddressType: props.ipAddressType, + }); + + this.securityGroup = props.securityGroup || new ec2.SecurityGroup(this, 'SecurityGroup', { + vpc: props.vpc, + description: `Automatically created Security Group for ELB ${this.uniqueId}` + }); + this.connections = new ec2.Connections({ securityGroup: this.securityGroup }); + + if (props.http2Enabled === false) { this.setAttribute('routing.http2.enabled', 'false'); } + if (props.idleTimeoutSecs !== undefined) { this.setAttribute('idle_timeout.timeout_seconds', props.idleTimeoutSecs.toString()); } + } + + /** + * Enable access logging for this load balancer + */ + public logAccessLogs(bucket: s3.BucketRef, prefix?: string) { + this.setAttribute('access_logs.s3.enabled', 'true'); + this.setAttribute('access_logs.s3.bucket', bucket.bucketName.toString()); + this.setAttribute('access_logs.s3.prefix', prefix); + + const stack = cdk.Stack.find(this); + + const region = stack.requireRegion('Enable ELBv2 access logging'); + const account = ELBV2_ACCOUNTS[region]; + if (!account) { + throw new Error(`Cannot enable access logging; don't know ELBv2 account for region ${region}`); + } + + // FIXME: can't use grantPut() here because that only takes IAM objects, not arbitrary principals + bucket.addToResourcePolicy(new cdk.PolicyStatement() + .addPrincipal(new cdk.AccountPrincipal(account)) + .addAction('s3:PutObject') + .addResource(bucket.arnForObjects(prefix || '', '*'))); + } + + /** + * Add a new listener to this load balancer + */ + public addListener(id: string, props: BaseApplicationListenerProps): ApplicationListener { + return new ApplicationListener(this, id, { + loadBalancer: this, + ...props + }); + } +} + +/** + * An application load balancer + */ +export interface IApplicationLoadBalancer extends ec2.IConnectable { + /** + * The ARN of this load balancer + */ + readonly loadBalancerArn: LoadBalancerArn; + + /** + * The VPC this load balancer has been created in (if available) + */ + readonly vpc?: ec2.VpcNetworkRef; + + /** + * Add a new listener to this load balancer + */ + addListener(id: string, props: BaseApplicationListenerProps): ApplicationListener; +} + +// https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-access-logs.html#access-logging-bucket-permissions +const ELBV2_ACCOUNTS: {[region: string]: string } = { + 'us-east-1': '127311923021', + 'us-east-2': '033677994240', + 'us-west-1': '027434742980', + 'us-west-2': '797873946194', + 'ca-central-1': '985666609251', + 'eu-central-1': '054676820928', + 'eu-west-1': '156460612806', + 'eu-west-2': '652711504416', + 'eu-west-3': '009996457667', + 'ap-northeast-1': '582318560864', + 'ap-northeast-2': '600734575887', + 'ap-northeast-3': '383597477331', + 'ap-southeast-1': '114774131450', + 'ap-southeast-2': '783225319266', + 'ap-south-1': '718504428378', + 'sa-east-1': '507241528517', + 'us-gov-west-1': '048591011584', + 'cn-north-1': '638102146993', + 'cn-northwest-1': '037604701340', +}; + +/** + * An ApplicationLoadBalancer that has been defined elsewhere + */ +class ImportedApplicationLoadBalancer extends BaseImportedLoadBalancer implements IApplicationLoadBalancer, ec2.IConnectable { + // FIXME: Proper security group export? + public readonly connections: ec2.Connections = new ec2.ImportedConnections(); + public readonly vpc?: ec2.VpcNetworkRef; + + public addListener(id: string, props: BaseApplicationListenerProps): ApplicationListener { + return new ApplicationListener(this, id, { + loadBalancer: this, + ...props + }); + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts new file mode 100644 index 0000000000000..65e67f832d5fb --- /dev/null +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts @@ -0,0 +1,197 @@ +import ec2 = require('@aws-cdk/aws-ec2'); +import cdk = require('@aws-cdk/cdk'); +import { BaseTargetGroup, BaseTargetGroupProps, ITargetGroup, LoadBalancerTargetProps, TargetGroupRefProps } from '../shared/base-target-group'; +import { ApplicationProtocol } from '../shared/enums'; +import { BaseImportedTargetGroup } from '../shared/imported'; +import { determineProtocolAndPort } from '../shared/util'; +import { IApplicationListener } from './application-listener'; + +/** + * Properties for defining an Application Target Group + */ +export interface ApplicationTargetGroupProps extends BaseTargetGroupProps { + /** + * The protocol to use + * + * @default Determined from port if known + */ + protocol?: ApplicationProtocol; + + /** + * The port on which the listener listens for requests. + * + * @default Determined from protocol if known + */ + port?: number; + + /** + * The time period during which the load balancer sends a newly registered + * target a linearly increasing share of the traffic to the target group. + * + * The range is 30–900 seconds (15 minutes). + * + * @default 0 + */ + slowStartSec?: number; + + /** + * The stickiness cookie expiration period. + * + * Setting this value enables load balancer stickiness. + * + * After this period, the cookie is considered stale. The minimum value is + * 1 second and the maximum value is 7 days (604800 seconds). + * + * @default 86400 (1 day) + */ + stickinessCookieDurationSec?: number; + + /** + * The targets to add to this target group. + * + * Can be `Instance`, `IPAddress`, or any self-registering load balancing + * target. If you use either `Instance` or `IPAddress` as targets, all + * target must be of the same type. + */ + targets?: IApplicationLoadBalancerTarget[]; +} + +/** + * Define an Application Target Group + */ +export class ApplicationTargetGroup extends BaseTargetGroup { + /** + * Import an existing target group + */ + public static import(parent: cdk.Construct, id: string, props: TargetGroupRefProps): IApplicationTargetGroup { + return new ImportedApplicationTargetGroup(parent, id, props); + } + + private readonly connectableMembers: ConnectableMember[]; + private readonly listeners: IApplicationListener[]; + + constructor(parent: cdk.Construct, id: string, props: ApplicationTargetGroupProps) { + const [protocol, port] = determineProtocolAndPort(props.protocol, props.port); + + super(parent, id, props, { + protocol, + port, + }); + + this.connectableMembers = []; + this.listeners = []; + + if (props.slowStartSec !== undefined) { + this.setAttribute('slow_start.duration_seconds', props.slowStartSec.toString()); + } + if (props.stickinessCookieDurationSec !== undefined) { + this.enableCookieStickiness(props.stickinessCookieDurationSec); + } + + this.addTarget(...(props.targets || [])); + } + + /** + * Add a load balancing target to this target group + */ + public addTarget(...targets: IApplicationLoadBalancerTarget[]) { + for (const target of targets) { + const result = target.attachToApplicationTargetGroup(this); + this.addLoadBalancerTarget(result); + } + } + + /** + * Enable sticky routing via a cookie to members of this target group + */ + public enableCookieStickiness(durationSec: number) { + this.setAttribute('stickiness.enabled', 'true'); + this.setAttribute('stickiness.type', 'lb_cookie'); + this.setAttribute('stickiness.lb_cookie.duration_seconds', durationSec.toString()); + } + + /** + * Register a connectable as a member of this target group. + * + * Don't call this directly. It will be called by load balancing targets. + */ + public registerConnectable(connectable: ec2.IConnectable, portRange?: ec2.IPortRange) { + if (portRange === undefined) { + if (cdk.isToken(this.defaultPort)) { + portRange = new ec2.TcpPortFromAttribute(new cdk.Token(this.defaultPort)); + } else { + portRange = new ec2.TcpPort(parseInt(this.defaultPort, 10)); + } + } + + // Notify all listeners that we already know about of this new connectable. + // Then remember for new listeners that might get added later. + this.connectableMembers.push({ connectable, portRange }); + for (const listener of this.listeners) { + listener.registerConnectable(connectable, portRange); + } + } + + /** + * Register a listener that is load balancing to this target group. + * + * Don't call this directly. It will be called by listeners. + */ + public registerListener(listener: IApplicationListener) { + // Notify this listener of all connectables that we know about. + // Then remember for new connectables that might get added later. + for (const member of this.connectableMembers) { + listener.registerConnectable(member.connectable, member.portRange); + } + this.listeners.push(listener); + } +} + +/** + * A connectable member of a target group + */ +interface ConnectableMember { + /** + * The connectable member + */ + connectable: ec2.IConnectable; + + /** + * The port (range) the member is listening on + */ + portRange: ec2.IPortRange; +} + +/** + * A Target Group for Application Load Balancers + */ +export interface IApplicationTargetGroup extends ITargetGroup { + /** + * Register a listener that is load balancing to this target group. + * + * Don't call this directly. It will be called by listeners. + */ + registerListener(listener: IApplicationListener): void; +} + +/** + * An imported application target group + */ +class ImportedApplicationTargetGroup extends BaseImportedTargetGroup implements IApplicationTargetGroup { + public registerListener(_listener: IApplicationListener) { + // Nothing to do, we know nothing of our members + } +} + +/** + * Interface for constructs that can be targets of an application load balancer + */ +export interface IApplicationLoadBalancerTarget { + /** + * Attach load-balanced target to a TargetGroup + * + * May return JSON to directly add to the [Targets] list, or return undefined + * if the target will register itself with the load balancer. + */ + attachToApplicationTargetGroup(targetGroup: ApplicationTargetGroup): LoadBalancerTargetProps; +} diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/index.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/index.ts index b9ea0164b6480..ca5dda9e65c2a 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/index.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/index.ts @@ -1,12 +1,18 @@ // AWS::ElasticLoadBalancingV2 CloudFormation Resources: export * from './elasticloadbalancingv2.generated'; -export * from './listener'; -export * from './listener-ref'; -export * from './listener-rule'; -export * from './listener-certificate'; -export * from './load-balancer'; -export * from './load-balancer-ref'; -export * from './target-group'; -export * from './target-group-ref'; -export * from './types'; \ No newline at end of file +export * from './alb/application-listener'; +export * from './alb/application-listener-certificate'; +export * from './alb/application-listener-rule'; +export * from './alb/application-load-balancer'; +export * from './alb/application-target-group'; + +export * from './nlb/network-listener'; +export * from './nlb/network-load-balancer'; +export * from './nlb/network-target-group'; + +export * from './shared/base-listener'; +export * from './shared/base-load-balancer'; +export * from './shared/base-target-group'; +export * from './shared/enums'; +export * from './shared/load-balancer-targets'; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/listener-ref.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/listener-ref.ts deleted file mode 100644 index 6b0b7f7eb447f..0000000000000 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/listener-ref.ts +++ /dev/null @@ -1,96 +0,0 @@ -import ec2 = require('@aws-cdk/aws-ec2'); -import cdk = require('@aws-cdk/cdk'); -import { ListenerArn } from './elasticloadbalancingv2.generated'; -import { BaseListenerRuleProps, ListenerRule } from './listener-rule'; -import { TargetGroupRef } from './target-group-ref'; - -/** - * A listener - */ -export abstract class ListenerRef extends cdk.Construct implements ec2.IConnectable { - /** - * Import an existing listener - */ - public static import(parent: cdk.Construct, id: string, props: ListenerRefProps): ListenerRef { - return new ImportedListener(parent, id, props); - } - - /** - * Class to give to target group so it can access private functions - */ - private static Internals = class implements IListenerInternals { - constructor(private readonly listener: ListenerRef) { - } - - public registerConnectable(connectable: ec2.IConnectable): void { - this.listener.connections.allowDefaultPortTo(connectable, 'Load balancer to target'); - } - }; - - /** - * ARN of the listener - */ - public abstract readonly listenerArn: ListenerArn; - - /** - * Connections for this listener - */ - public readonly abstract connections: ec2.Connections; - - /** - * Export this listener - */ - public export(): ListenerRefProps { - return { - listenerArn: new ListenerArn(new cdk.Output(this, 'ListenerArn', { value: this.listenerArn }).makeImportValue()) - }; - } - - /** - * Add a rule to this listener - */ - public addRule(id: string, props: BaseListenerRuleProps) { - return new ListenerRule(this, id, { - listener: this, - ...props - }); - } - - /** - * Register a target group with this listener - */ - protected registerTargetGroup(targetGroup: TargetGroupRef) { - targetGroup.bindToListener(new ListenerRef.Internals(this)); - } -} - -/** - * Properties to reference an existing listener - */ -export interface ListenerRefProps { - /** - * ARN of the listener - */ - listenerArn: ListenerArn; -} - -/** - * An existing listener - */ -class ImportedListener extends ListenerRef { - public readonly listenerArn: ListenerArn; - public readonly connections: ec2.Connections = new ec2.ImportedConnections(); - - constructor(parent: cdk.Construct, id: string, props: ListenerRefProps) { - super(parent, id); - - this.listenerArn = props.listenerArn; - } -} - -/** - * Parts of the listener that only Target Groups should have access to - */ -export interface IListenerInternals { - registerConnectable(connectable: ec2.IConnectable): void; -} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/listener.ts deleted file mode 100644 index 0c01cf083531a..0000000000000 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/listener.ts +++ /dev/null @@ -1,156 +0,0 @@ -import ec2 = require('@aws-cdk/aws-ec2'); -import cdk = require('@aws-cdk/cdk'); -import { cloudformation, ListenerArn } from './elasticloadbalancingv2.generated'; -import { ListenerRef } from './listener-ref'; -import { LoadBalancerRef } from './load-balancer-ref'; -import { TargetGroupRef } from './target-group-ref'; -import { Protocol } from './types'; -import { determineProtocolAndPort } from './util'; - -/** - * Properties for a listener on a load balancer - */ -export interface BaseListenerProps { - /** - * The protocol to use - * - * @default Determined from port if known - */ - protocol?: Protocol; - - /** - * The port on which the listener listens for requests. - * - * @default Determined from protocol if known - */ - port?: number; - - /** - * The certificates to use on this listener - */ - certificateArns?: cdk.Arn[]; - - /** - * The security policy that defines which ciphers and protocols are supported. - * - * @default the current predefined security policy. - */ - sslPolicy?: SslPolicy; -} - -/** - * Properties for defining a listener - */ -export interface ListenerProps extends BaseListenerProps { - /** - * The load balancer to attach this listener to - */ - loadBalancer: LoadBalancerRef; -} - -/** - * Define a listener - */ -export class Listener extends ListenerRef { - public readonly connections: ec2.Connections; - public readonly listenerArn: ListenerArn; - private readonly defaultActions: any[] = []; - - constructor(parent: cdk.Construct, id: string, props: ListenerProps) { - super(parent, id); - - const [protocol, port] = determineProtocolAndPort(props.protocol, props.port); - - const resource = new cloudformation.ListenerResource(this, 'Resource', { - loadBalancerArn: props.loadBalancer.loadBalancerArn, - certificates: props.certificateArns, - protocol, - port, - sslPolicy: props.sslPolicy, - defaultActions: new cdk.Token(() => this.renderActions()) - }); - - // This listener edits the securitygroup of the load balancer, - // but adds its own default port. - this.connections = new ec2.Connections({ - securityGroup: props.loadBalancer.connections.securityGroup, - defaultPortRange: new ec2.TcpPort(port), - }); - - this.listenerArn = resource.ref; - } - - /** - * Add a TargetGroup to load balance to - */ - public addDefaultTargetGroup(targetGroup: TargetGroupRef) { - this.defaultActions.push({ - targetGroupArn: targetGroup.targetGroupArn, - type: 'forward' - }); - this.registerTargetGroup(targetGroup); - return targetGroup; - } - - private renderActions() { - if (this.defaultActions.length === 0) { - throw new Error('Listener needs at least one default action'); - } - return this.defaultActions; - } -} - -/** - * Elastic Load Balancing provides the following security policies for Application Load Balancers - * - * We recommend the Recommended policy for general use. You can - * use the ForwardSecrecy policy if you require Forward Secrecy - * (FS). - * - * You can use one of the TLS policies to meet compliance and security - * standards that require disabling certain TLS protocol versions, or to - * support legacy clients that require deprecated ciphers. - * - * @see https://docs.aws.amazon.com/elasticloadbalancing/latest/application/create-https-listener.html - */ -export enum SslPolicy { - /** - * The recommended security policy - */ - Recommended = 'ELBSecurityPolicy-2016-08', - - /** - * Forward secrecy ciphers only - */ - ForwardSecrecy = 'ELBSecurityPolicy-FS-2018-06', - - /** - * TLS1.2 only and no SHA ciphers - */ - TLS12 = 'ELBSecurityPolicy-TLS-1-2-2017-01', - - /** - * TLS1.2 only with all ciphers - */ - TLS12Ext = 'ELBSecurityPolicy-TLS-1-2-Ext-2018-06', - - /** - * TLS1.1 and higher with all ciphers - */ - TLS11 = 'ELBSecurityPolicy-TLS-1-1-2017-01', - - /** - * Support for DES-CBC3-SHA - * - * Do not use this security policy unless you must support a legacy client - * that requires the DES-CBC3-SHA cipher, which is a weak cipher. - */ - Legacy = 'ELBSecurityPolicy-TLS-1-0-2015-04', -} - -/** - * - */ -export interface Action { - targetGroup: TargetGroupRef; -} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/load-balancer-ref.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/load-balancer-ref.ts deleted file mode 100644 index a8da8201136e9..0000000000000 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/load-balancer-ref.ts +++ /dev/null @@ -1,69 +0,0 @@ -import ec2 = require('@aws-cdk/aws-ec2'); -import cdk = require('@aws-cdk/cdk'); -import { LoadBalancerArn } from './elasticloadbalancingv2.generated'; -import { BaseListenerProps, Listener } from './listener'; - -/** - * A load balancer - */ -export abstract class LoadBalancerRef extends cdk.Construct implements ec2.IConnectable { - /** - * Import an existing Load Balancer - */ - public static import(parent: cdk.Construct, id: string, props: LoadBalancerRefProps): LoadBalancerRef { - return new ImportedLoadBalancer(parent, id, props); - } - - /** - * ARN of the load balancer - */ - public abstract readonly loadBalancerArn: LoadBalancerArn; - - /** - * Connections for this load balancer - */ - public abstract readonly connections: ec2.Connections; - - /** - * Export this load balancer - */ - public export(): LoadBalancerRefProps { - return { - loadBalancerArn: new LoadBalancerArn(new cdk.Output(this, 'LoadBalancerArn', { value: this.loadBalancerArn }).makeImportValue()) - }; - } - - /** - * Add a listener to this load balancer - */ - public addListener(id: string, props: BaseListenerProps) { - return new Listener(this, id, { - loadBalancer: this, - ...props - }); - } -} - -/** - * Properties to reference an existing load balancer - */ -export interface LoadBalancerRefProps { - /** - * ARN of the load balancer - */ - loadBalancerArn: LoadBalancerArn; -} - -/** - * An existing load balancer - */ -class ImportedLoadBalancer extends LoadBalancerRef { - public readonly loadBalancerArn: LoadBalancerArn; - public readonly connections: ec2.Connections = new ec2.ImportedConnections(); - - constructor(parent: cdk.Construct, id: string, props: LoadBalancerRefProps) { - super(parent, id); - - this.loadBalancerArn = props.loadBalancerArn; - } -} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/load-balancer.ts deleted file mode 100644 index 30e96dd9a84f8..0000000000000 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/load-balancer.ts +++ /dev/null @@ -1,233 +0,0 @@ -import ec2 = require('@aws-cdk/aws-ec2'); -import s3 = require('@aws-cdk/aws-s3'); -import cdk = require('@aws-cdk/cdk'); -import { cloudformation, LoadBalancerArn, LoadBalancerCanonicalHostedZoneId, - LoadBalancerDnsName, LoadBalancerFullName, LoadBalancerName } from './elasticloadbalancingv2.generated'; -import { LoadBalancerRef } from './load-balancer-ref'; -import { Attributes, renderAttributes } from './util'; - -/** - * Properties to define a load balancer - */ -export interface LoadBalancerProps { - /** - * Type of the load balancer - * - * @default Application - */ - type?: LoadBalancerType; - - /** - * Name of the load balancer - * - * @default Automatically generated name - */ - loadBalancerName?: string; - - /** - * The VPC network to place the load balancer in - */ - vpc: ec2.VpcNetworkRef; - - /** - * Whether the load balancer has an internet-routable address - * - * @default false - */ - internetFacing?: boolean; - - /** - * Where in the VPC to place the load balancer - * - * @default Public subnets if internetFacing, otherwise private subnets - */ - vpcPlacement?: ec2.VpcPlacementStrategy; - - /** - * Security group to associate with this load balancer - * - * @default A security group is created - */ - securityGroup?: ec2.SecurityGroupRef; - - /** - * The type of IP addresses to use - * - * Only applies to application load balancers. - * - * @default IpAddressType.Ipv4 - */ - ipAddressType?: IpAddressType; - - /** - * Indicates whether deletion protection is enabled. - * - * @default false - */ - deletionProtection?: boolean; - - /** - * Indicates whether HTTP/2 is enabled. - * - * @default true - */ - http2Enabled?: boolean; - - /** - * The load balancer idle timeout, in seconds - * - * @default 60 - */ - idleTimeoutSecs?: number; - - /** - * Indicates whether cross-zone load balancing is enabled. - * - * @default false - */ - crossZoneEnabled?: boolean; -} - -/** - * Define a load balancer - */ -export class LoadBalancer extends LoadBalancerRef implements ec2.IConnectable { - public readonly canonicalHostedZoneId: LoadBalancerCanonicalHostedZoneId; - public readonly dnsName: LoadBalancerDnsName; - public readonly fullName: LoadBalancerFullName; - public readonly loadBalancerName: LoadBalancerName; - public readonly loadBalancerArn: LoadBalancerArn; - public readonly connections: ec2.Connections; - public readonly type: LoadBalancerType; - private readonly attributes: Attributes = {}; - - constructor(parent: cdk.Construct, id: string, props: LoadBalancerProps) { - super(parent, id); - - this.type = props.type || LoadBalancerType.Application; - - if (this.type !== LoadBalancerType.Application && props.ipAddressType) { - throw new Error('ipAddressType can only be supplied for application load balancers'); - } - - const securityGroup = props.securityGroup || new ec2.SecurityGroup(this, 'SecurityGroup', { - vpc: props.vpc, - description: `Automatically created Security Group for ELB ${this.uniqueId}` - }); - const internetFacing = ifUndefined(props.internetFacing, false); - - const subnets = props.vpc.subnets(ifUndefined(props.vpcPlacement, - { subnetsToUse: internetFacing ? ec2.SubnetType.Public : ec2.SubnetType.Private })); - - const resource = new cloudformation.LoadBalancerResource(this, 'Resource', { - type: this.type, - loadBalancerName: props.loadBalancerName, - subnets: subnets.map(s => s.subnetId), - securityGroups: [securityGroup.securityGroupId], - scheme: internetFacing ? 'internet-facing' : 'internal', - ipAddressType: props.ipAddressType, - loadBalancerAttributes: new cdk.Token(() => renderAttributes(this.attributes)), - }); - - if (props.deletionProtection) { this.attributes['deletion_protection.enabled'] = 'true'; } - if (props.http2Enabled === false) { this.attributes['routing.http2.enabled'] = 'false'; } - if (props.idleTimeoutSecs !== undefined) { this.attributes['idle_timeout.timeout_seconds'] = props.idleTimeoutSecs.toString(); } - if (props.crossZoneEnabled) { this.attributes['load_balancing.cross_zone.enabled'] = 'true'; } - - this.connections = new ec2.Connections({ securityGroup }); - this.canonicalHostedZoneId = resource.loadBalancerCanonicalHostedZoneId; - this.dnsName = resource.loadBalancerDnsName; - this.fullName = resource.loadBalancerFullName; - this.loadBalancerName = resource.loadBalancerName; - this.loadBalancerArn = resource.ref; - } - - /** - * Enable access logging for this load balancer - */ - public logAccessLogs(bucket: s3.BucketRef, prefix?: string) { - this.attributes['access_logs.s3.enabled'] = 'true'; - this.attributes['access_logs.s3.bucket'] = bucket.bucketName.toString(); - this.attributes['access_logs.s3.prefix'] = prefix; - - const stack = cdk.Stack.find(this); - - const region = stack.requireRegion('Enable ELBv2 access logging'); - const account = ELBV2_ACCOUNTS[region]; - if (!account) { - throw new Error(`Cannot enable access logging; don't know ELBv2 account for region ${region}`); - } - - // FIXME: can't use grantPut() here because that only takes IAM objects, not arbitrary principals - bucket.addToResourcePolicy(new cdk.PolicyStatement() - .addPrincipal(new cdk.AccountPrincipal(account)) - .addAction('s3:PutObject') - .addResource(bucket.arnForObjects(prefix || '', '*'))); - } - - /** - * Set a non-standard attribute on the load balancer - * - * @see https://docs.aws.amazon.com/elasticloadbalancing/latest/application/application-load-balancers.html#load-balancer-attributes - */ - public setAttribute(key: string, value: string | undefined) { - this.attributes[key] = value; - } -} - -/** - * The type of the load balancer - */ -export enum LoadBalancerType { - /** - * An application load balancer - */ - Application = 'application', - - /** - * A network load balancer - */ - Network = 'network', -} - -/** - * What kind of addresses to allocate to the load balancer - */ -export enum IpAddressType { - /** - * Allocate IPv4 addresses - */ - Ipv4 = 'ipv4', - - /** - * Allocate both IPv4 and IPv6 addresses - */ - DualStack = 'dualstack', -} - -function ifUndefined(x: T | undefined, def: T) { - return x !== undefined ? x : def; -} - -// https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-access-logs.html#access-logging-bucket-permissions -const ELBV2_ACCOUNTS: {[region: string]: string } = { - 'us-east-1': '127311923021', - 'us-east-2': '033677994240', - 'us-west-1': '027434742980', - 'us-west-2': '797873946194', - 'ca-central-1': '985666609251', - 'eu-central-1': '054676820928', - 'eu-west-1': '156460612806', - 'eu-west-2': '652711504416', - 'eu-west-3': '009996457667', - 'ap-northeast-1': '582318560864', - 'ap-northeast-2': '600734575887', - 'ap-northeast-3': '383597477331', - 'ap-southeast-1': '114774131450', - 'ap-southeast-2': '783225319266', - 'ap-south-1': '718504428378', - 'sa-east-1': '507241528517', - 'us-gov-west-1': '048591011584', - 'cn-north-1': '638102146993', - 'cn-northwest-1': '037604701340', -}; diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener.ts new file mode 100644 index 0000000000000..c3b0ff884ced8 --- /dev/null +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener.ts @@ -0,0 +1,172 @@ +import cdk = require('@aws-cdk/cdk'); +import { ListenerArn } from '../elasticloadbalancingv2.generated'; +import { BaseListener, ListenerRefProps } from '../shared/base-listener'; +import { HealthCheck } from '../shared/base-target-group'; +import { Protocol } from '../shared/enums'; +import { BaseImportedListener } from '../shared/imported'; +import { INetworkLoadBalancer } from './network-load-balancer'; +import { INetworkLoadBalancerTarget, INetworkTargetGroup, NetworkTargetGroup } from './network-target-group'; + +/** + * Basic properties for a Network Listener + */ +export interface BaseNetworkListenerProps { + /** + * The port on which the listener listens for requests. + */ + port: number; + + /** + * Default target groups to load balance to + * + * @default None + */ + defaultTargetGroups?: INetworkTargetGroup[]; +} + +/** + * Properties for a Network Listener attached to a Load Balancer + */ +export interface NetworkListenerProps extends BaseNetworkListenerProps { + /** + * The load balancer to attach this listener to + */ + loadBalancer: INetworkLoadBalancer; +} + +/** + * Define a Network Listener + */ +export class NetworkListener extends BaseListener implements INetworkListener { + /** + * Import an existing listener + */ + public static import(parent: cdk.Construct, id: string, props: ListenerRefProps): INetworkListener { + return new ImportedNetworkListener(parent, id, props); + } + + /** + * The load balancer this listener is attached to + */ + private readonly loadBalancer: INetworkLoadBalancer; + + constructor(parent: cdk.Construct, id: string, props: NetworkListenerProps) { + super(parent, id, { + loadBalancerArn: props.loadBalancer.loadBalancerArn, + protocol: Protocol.Tcp, + port: props.port, + }); + + this.loadBalancer = props.loadBalancer; + } + + /** + * Load balance incoming requests to the given target groups. + */ + public addTargetGroups(_id: string, ...targetGroups: INetworkTargetGroup[]): void { + // New default target(s) + for (const targetGroup of targetGroups) { + this._addDefaultTargetGroup(targetGroup); + } + } + + /** + * Load balance incoming requests to the given load balancing targets. + * + * This method implicitly creates an ApplicationTargetGroup for the targets + * involved. + * + * @returns The newly created target group + */ + public addTargets(id: string, props: AddNetworkTargetsProps): NetworkTargetGroup { + if (!this.loadBalancer.vpc) { + // tslint:disable-next-line:max-line-length + throw new Error('Can only call addTargets() when using a constructed Load Balancer; construct a new TargetGroup and use addTargetGroup'); + } + + const group = new NetworkTargetGroup(this, id + 'Group', { + deregistrationDelaySec: props.deregistrationDelaySec, + healthCheck: props.healthCheck, + port: props.port, + proxyProtocolV2: props.proxyProtocolV2, + targetGroupName: props.targetGroupName, + targets: props.targets, + vpc: this.loadBalancer.vpc, + }); + + this.addTargetGroups(id, group); + + return group; + } +} + +/** + * Properties to reference an existing listener + */ +export interface INetworkListener { + /** + * ARN of the listener + */ + listenerArn: ListenerArn; +} + +/** + * An imported Network Listener + */ +class ImportedNetworkListener extends BaseImportedListener implements INetworkListener { +} + +/** + * Properties for adding new network targets to a listener + */ +export interface AddNetworkTargetsProps { + /** + * The port on which the listener listens for requests. + * + * @default Determined from protocol if known + */ + port: number; + + /** + * The targets to add to this target group. + * + * Can be `Instance`, `IPAddress`, or any self-registering load balancing + * target. If you use either `Instance` or `IPAddress` as targets, all + * target must be of the same type. + */ + targets?: INetworkLoadBalancerTarget[]; + + /** + * The name of the target group. + * + * This name must be unique per region per account, can have a maximum of + * 32 characters, must contain only alphanumeric characters or hyphens, and + * must not begin or end with a hyphen. + * + * @default Automatically generated + */ + targetGroupName?: string; + + /** + * The amount of time for Elastic Load Balancing to wait before deregistering a target. + * + * The range is 0–3600 seconds. + * + * @default 300 + */ + deregistrationDelaySec?: number; + + /** + * Indicates whether Proxy Protocol version 2 is enabled. + * + * @default false + */ + proxyProtocolV2?: boolean; + + /** + * Health check configuration + * + * @default No health check + */ + healthCheck?: HealthCheck; +} diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-load-balancer.ts new file mode 100644 index 0000000000000..9d5c440d96b3a --- /dev/null +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-load-balancer.ts @@ -0,0 +1,86 @@ +import ec2 = require('@aws-cdk/aws-ec2'); +import cdk = require('@aws-cdk/cdk'); +import { LoadBalancerArn } from '../elasticloadbalancingv2.generated'; +import { BaseLoadBalancer, BaseLoadBalancerProps, LoadBalancerRefProps } from '../shared/base-load-balancer'; +import { BaseImportedLoadBalancer } from '../shared/imported'; +import { BaseNetworkListenerProps, NetworkListener } from './network-listener'; + +/** + * Properties for a network load balancer + */ +export interface NetworkLoadBalancerProps extends BaseLoadBalancerProps { + /** + * Indicates whether cross-zone load balancing is enabled. + * + * @default false + */ + crossZoneEnabled?: boolean; +} + +/** + * Define a new network load balancer + */ +export class NetworkLoadBalancer extends BaseLoadBalancer implements INetworkLoadBalancer { + public static import(parent: cdk.Construct, id: string, props: LoadBalancerRefProps): INetworkLoadBalancer { + return new ImportedNetworkLoadBalancer(parent, id, props); + } + + constructor(parent: cdk.Construct, id: string, props: NetworkLoadBalancerProps) { + super(parent, id, props, { + type: "network", + }); + + if (props.crossZoneEnabled) { this.setAttribute('load_balancing.cross_zone.enabled', 'true'); } + } + + /** + * Add a listener to this load balancer + * + * @returns The newly created listener + */ + public addListener(id: string, props: BaseNetworkListenerProps): NetworkListener { + return new NetworkListener(this, id, { + loadBalancer: this, + ...props + }); + } +} + +/** + * A network load balancer + */ +export interface INetworkLoadBalancer { + /** + * The ARN of this load balancer + */ + readonly loadBalancerArn: LoadBalancerArn; + + /** + * The VPC this load balancer has been created in (if available) + */ + readonly vpc?: ec2.VpcNetworkRef; + + /** + * Add a listener to this load balancer + * + * @returns The newly created listener + */ + addListener(id: string, props: BaseNetworkListenerProps): NetworkListener; +} + +/** + * An imported network load balancer + */ +class ImportedNetworkLoadBalancer extends BaseImportedLoadBalancer implements INetworkLoadBalancer { + /** + * Add a listener to this load balancer + * + * @returns The newly created listener + */ + public addListener(id: string, props: BaseNetworkListenerProps): NetworkListener { + return new NetworkListener(this, id, { + loadBalancer: this, + ...props + }); + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-target-group.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-target-group.ts new file mode 100644 index 0000000000000..d03acd72412bb --- /dev/null +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-target-group.ts @@ -0,0 +1,91 @@ +import cdk = require('@aws-cdk/cdk'); +import { BaseTargetGroup, BaseTargetGroupProps, ITargetGroup, LoadBalancerTargetProps, TargetGroupRefProps } from '../shared/base-target-group'; +import { Protocol } from '../shared/enums'; +import { BaseImportedTargetGroup } from '../shared/imported'; + +/** + * Properties for a new Network Target Group + */ +export interface NetworkTargetGroupProps extends BaseTargetGroupProps { + /** + * The port on which the listener listens for requests. + */ + port: number; + + /** + * Indicates whether Proxy Protocol version 2 is enabled. + * + * @default false + */ + proxyProtocolV2?: boolean; + + /** + * The targets to add to this target group. + * + * Can be `Instance`, `IPAddress`, or any self-registering load balancing + * target. If you use either `Instance` or `IPAddress` as targets, all + * target must be of the same type. + */ + targets?: INetworkLoadBalancerTarget[]; +} + +/** + * Define a Network Target Group + */ +export class NetworkTargetGroup extends BaseTargetGroup { + /** + * Import an existing listener + */ + public static import(parent: cdk.Construct, id: string, props: TargetGroupRefProps): INetworkTargetGroup { + return new ImportedNetworkTargetGroup(parent, id, props); + } + + constructor(parent: cdk.Construct, id: string, props: NetworkTargetGroupProps) { + super(parent, id, props, { + protocol: Protocol.Tcp, + port: props.port, + }); + + if (props.proxyProtocolV2) { + this.setAttribute('proxy_protocol_v2.enabled', 'true'); + } + + this.addTarget(...(props.targets || [])); + } + + /** + * Add a load balancing target to this target group + */ + public addTarget(...targets: INetworkLoadBalancerTarget[]) { + for (const target of targets) { + const result = target.attachToNetworkTargetGroup(this); + this.addLoadBalancerTarget(result); + } + } +} + +/** + * A network target group + */ +// tslint:disable-next-line:no-empty-interface +export interface INetworkTargetGroup extends ITargetGroup { +} + +/** + * An imported network target group + */ +class ImportedNetworkTargetGroup extends BaseImportedTargetGroup implements INetworkTargetGroup { +} + +/** + * Interface for constructs that can be targets of an network load balancer + */ +export interface INetworkLoadBalancerTarget { + /** + * Attach load-balanced target to a TargetGroup + * + * May return JSON to directly add to the [Targets] list, or return undefined + * if the target will register itself with the load balancer. + */ + attachToNetworkTargetGroup(targetGroup: NetworkTargetGroup): LoadBalancerTargetProps; +} diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-listener.ts new file mode 100644 index 0000000000000..979c28548143a --- /dev/null +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-listener.ts @@ -0,0 +1,61 @@ +import cdk = require('@aws-cdk/cdk'); +import { cloudformation, ListenerArn } from '../elasticloadbalancingv2.generated'; +import { ITargetGroup } from './base-target-group'; + +/** + * Base class for listeners + */ +export abstract class BaseListener extends cdk.Construct { + public readonly listenerArn: ListenerArn; + private readonly defaultActions: any[] = []; + + constructor(parent: cdk.Construct, id: string, additionalProps: any) { + super(parent, id); + + const resource = new cloudformation.ListenerResource(this, 'Resource', { + ...additionalProps, + defaultActions: new cdk.Token(() => this.defaultActions), + }); + + this.listenerArn = resource.ref; + } + + /** + * Export this listener + */ + public export(): ListenerRefProps { + return { + listenerArn: new ListenerArn(new cdk.Output(this, 'ListenerArn', { value: this.listenerArn }).makeImportValue()) + }; + } + + /** + * Validate this listener + */ + public validate(): string[] { + if (this.defaultActions.length === 0) { + return ['Listener needs at least one default target group (call addTargetGroups)']; + } + return []; + } + + /** + * Add a TargetGroup to the list of default actions of this listener + */ + protected _addDefaultTargetGroup(targetGroup: ITargetGroup) { + this.defaultActions.push({ + targetGroupArn: targetGroup.targetGroupArn, + type: 'forward' + }); + } +} + +/** + * Properties to reference an existing listener + */ +export interface ListenerRefProps { + /** + * ARN of the listener + */ + listenerArn: ListenerArn; +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts new file mode 100644 index 0000000000000..f279de8156155 --- /dev/null +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts @@ -0,0 +1,157 @@ +import ec2 = require('@aws-cdk/aws-ec2'); +import cdk = require('@aws-cdk/cdk'); +import { cloudformation, LoadBalancerArn, LoadBalancerCanonicalHostedZoneId, LoadBalancerDnsName, + LoadBalancerFullName, LoadBalancerName } from '../elasticloadbalancingv2.generated'; +import { Attributes, ifUndefined, renderAttributes } from './util'; + +/** + * Shared properties of both Application and Network Load Balancers + */ +export interface BaseLoadBalancerProps { + /** + * Name of the load balancer + * + * @default Automatically generated name + */ + loadBalancerName?: string; + + /** + * The VPC network to place the load balancer in + */ + vpc: ec2.VpcNetworkRef; + + /** + * Whether the load balancer has an internet-routable address + * + * @default false + */ + internetFacing?: boolean; + + /** + * Where in the VPC to place the load balancer + * + * @default Public subnets if internetFacing, otherwise private subnets + */ + vpcPlacement?: ec2.VpcPlacementStrategy; + + /** + * Indicates whether deletion protection is enabled. + * + * @default false + */ + deletionProtection?: boolean; +} + +/** + * Base class for both Application and Network Load Balancers + */ +export abstract class BaseLoadBalancer extends cdk.Construct { + /** + * The canonical hosted zone ID of this load balancer + * + * @example Z2P70J7EXAMPLE + */ + public readonly canonicalHostedZoneId: LoadBalancerCanonicalHostedZoneId; + + /** + * The DNS name of this load balancer + * + * @example my-load-balancer-424835706.us-west-2.elb.amazonaws.com + */ + public readonly dnsName: LoadBalancerDnsName; + + /** + * The full name of this load balancer + * + * @example app/my-load-balancer/50dc6c495c0c9188 + */ + public readonly fullName: LoadBalancerFullName; + + /** + * The name of this load balancer + * + * @example my-load-balancer + */ + public readonly loadBalancerName: LoadBalancerName; + + /** + * The ARN of this load balancer + * + * @example arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-internal-load-balancer/50dc6c495c0c9188 + */ + public readonly loadBalancerArn: LoadBalancerArn; + + /** + * The VPC this load balancer has been created in, if available + * + * If the Load Balancer was imported, the VPC is not available. + */ + public readonly vpc?: ec2.VpcNetworkRef; + + /** + * Attributes set on this load balancer + */ + private readonly attributes: Attributes = {}; + + constructor(parent: cdk.Construct, id: string, baseProps: BaseLoadBalancerProps, additionalProps: any) { + super(parent, id); + + const internetFacing = ifUndefined(baseProps.internetFacing, false); + + const subnets = baseProps.vpc.subnets(ifUndefined(baseProps.vpcPlacement, + { subnetsToUse: internetFacing ? ec2.SubnetType.Public : ec2.SubnetType.Private })); + + this.vpc = baseProps.vpc; + + const resource = new cloudformation.LoadBalancerResource(this, 'Resource', { + loadBalancerName: baseProps.loadBalancerName, + subnets: subnets.map(s => s.subnetId), + scheme: internetFacing ? 'internet-facing' : 'internal', + loadBalancerAttributes: new cdk.Token(() => renderAttributes(this.attributes)), + ...additionalProps + }); + + if (baseProps.deletionProtection) { this.setAttribute('deletion_protection.enabled', 'true'); } + + this.canonicalHostedZoneId = resource.loadBalancerCanonicalHostedZoneId; + this.dnsName = resource.loadBalancerDnsName; + this.fullName = resource.loadBalancerFullName; + this.loadBalancerName = resource.loadBalancerName; + this.loadBalancerArn = resource.ref; + } + + /** + * Set a non-standard attribute on the load balancer + * + * @see https://docs.aws.amazon.com/elasticloadbalancing/latest/application/application-load-balancers.html#load-balancer-attributes + */ + public setAttribute(key: string, value: string | undefined) { + this.attributes[key] = value; + } + + /** + * Remove an attribute from the load balancer + */ + public removeAttribute(key: string) { + this.setAttribute(key, undefined); + } + + /** + * Export this load balancer + */ + public export(): LoadBalancerRefProps { + return { + loadBalancerArn: new LoadBalancerArn(new cdk.Output(this, 'LoadBalancerArn', { value: this.loadBalancerArn }).makeImportValue()) + }; + } +} + +/** + * Properties to reference an existing load balancer + */ +export interface LoadBalancerRefProps { + /** + * ARN of the load balancer + */ + loadBalancerArn: LoadBalancerArn; +} diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts new file mode 100644 index 0000000000000..58a4bfe51424e --- /dev/null +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts @@ -0,0 +1,295 @@ +import ec2 = require('@aws-cdk/aws-ec2'); +import cdk = require('@aws-cdk/cdk'); +import { cloudformation, TargetGroupArn, TargetGroupFullName, TargetGroupName } from '../elasticloadbalancingv2.generated'; +import { Protocol, TargetType } from './enums'; +import { Attributes, renderAttributes } from './util'; + +/** + * Basic properties of both Application and Network Target Groups + */ +export interface BaseTargetGroupProps { + /** + * The name of the target group. + * + * This name must be unique per region per account, can have a maximum of + * 32 characters, must contain only alphanumeric characters or hyphens, and + * must not begin or end with a hyphen. + * + * @default Automatically generated + */ + targetGroupName?: string; + + /** + * The virtual private cloud (VPC). + */ + vpc: ec2.VpcNetworkRef; + + /** + * The amount of time for Elastic Load Balancing to wait before deregistering a target. + * + * The range is 0–3600 seconds. + * + * @default 300 + */ + deregistrationDelaySec?: number; + + /** + * Health check configuration + * + * @default No health check + */ + healthCheck?: HealthCheck; +} + +/** + * Properties for configuring a health check + */ +export interface HealthCheck { + /** + * The approximate number of seconds between health checks for an individual target. + * + * @default 30 + */ + intervalSecs?: number; + + /** + * The ping path destination where Elastic Load Balancing sends health check requests. + * + * @default / + */ + path?: string; + + /** + * The port that the load balancer uses when performing health checks on the targets. + * + * @default 'traffic-port' + */ + port?: string; + + /** + * The protocol the load balancer uses when performing health checks on targets. + * + * The TCP protocol is supported only if the protocol of the target group + * is TCP. + * + * @default HTTP for ALBs, TCP for NLBs + */ + protocol?: Protocol; + + /** + * The amount of time, in seconds, during which no response from a target means a failed health check. + * + * For Application Load Balancers, the range is 2–60 seconds and the + * default is 5 seconds. For Network Load Balancers, this is 10 seconds for + * TCP and HTTPS health checks and 6 seconds for HTTP health checks. + * + * @default 5 for ALBs, 10 or 6 for NLBs + */ + timeoutSeconds?: number; + + /** + * The number of consecutive health checks successes required before considering an unhealthy target healthy. + * + * For Application Load Balancers, the default is 5. For Network Load Balancers, the default is 3. + * + * @default 5 for ALBs, 3 for NLBs + */ + healthyThresholdCount?: number; + + /** + * The number of consecutive health check failures required before considering a target unhealthy. + * + * For Application Load Balancers, the default is 2. For Network Load + * Balancers, this value must be the same as the healthy threshold count. + * + * @default 2 + */ + unhealthyThresholdCount?: number; + + /** + * HTTP code to use when checking for a successful response from a target. + * + * For Application Load Balancers, you can specify values between 200 and + * 499, and the default value is 200. You can specify multiple values (for + * example, "200,202") or a range of values (for example, "200-299"). + */ + healthyHttpCodes?: string; +} + +/** + * Define the target of a load balancer + */ +export abstract class BaseTargetGroup extends cdk.Construct implements ITargetGroup { + /** + * The ARN of the target group + */ + public readonly targetGroupArn: TargetGroupArn; + + /** + * The full name of the target group + */ + public readonly targetGroupFullName: TargetGroupFullName; + + /** + * The name of the target group + */ + public readonly targetGroupName: TargetGroupName; + + /** + * Health check for the members of this target group + */ + public healthCheck: HealthCheck; + + /** + * Default port configured for members of this target group + */ + protected readonly defaultPort: string; + + /** + * Attributes of this target group + */ + private readonly attributes: Attributes = {}; + + /** + * The JSON objects returned by the directly registered members of this target group + */ + private readonly targetsJson = new Array(); + + /** + * The types of the directly registered members of this target group + */ + private targetType?: TargetType; + + /** + * The target group resource + */ + private readonly resource: cloudformation.TargetGroupResource; + + constructor(parent: cdk.Construct, id: string, baseProps: BaseTargetGroupProps, additionalProps: any) { + super(parent, id); + + if (baseProps.deregistrationDelaySec !== undefined) { + this.setAttribute('deregistration_delay.timeout_seconds', baseProps.deregistrationDelaySec.toString()); + } + + this.healthCheck = baseProps.healthCheck || {}; + + this.resource = new cloudformation.TargetGroupResource(this, 'Resource', { + targetGroupName: baseProps.targetGroupName, + targetGroupAttributes: new cdk.Token(() => renderAttributes(this.attributes)), + targetType: new cdk.Token(() => this.targetType), + targets: new cdk.Token(() => this.targetsJson), + vpcId: baseProps.vpc.vpcId, + + // HEALTH CHECK + healthCheckIntervalSeconds: new cdk.Token(() => this.healthCheck && this.healthCheck.intervalSecs), + healthCheckPath: new cdk.Token(() => this.healthCheck && this.healthCheck.path), + healthCheckPort: new cdk.Token(() => this.healthCheck && this.healthCheck.port), + healthCheckProtocol: new cdk.Token(() => this.healthCheck && this.healthCheck.protocol), + healthCheckTimeoutSeconds: new cdk.Token(() => this.healthCheck && this.healthCheck.timeoutSeconds), + healthyThresholdCount: new cdk.Token(() => this.healthCheck && this.healthCheck.healthyThresholdCount), + matcher: new cdk.Token(() => this.healthCheck && this.healthCheck.healthyHttpCodes !== undefined ? { + httpCode: this.healthCheck.healthyHttpCodes + } : undefined), + + ...additionalProps + }); + + this.targetGroupArn = this.resource.ref; + this.targetGroupFullName = this.resource.targetGroupFullName; + this.targetGroupName = this.resource.targetGroupName; + this.defaultPort = `${additionalProps.port}`; + } + + /** + * Set/replace the target group's health check + */ + public configureHealthCheck(healthCheck: HealthCheck) { + this.healthCheck = healthCheck; + } + + /** + * Set a non-standard attribute on the target group + * + * @see https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-target-groups.html#target-group-attributes + */ + public setAttribute(key: string, value: string | undefined) { + this.attributes[key] = value; + } + + /** + * Export this target group + */ + public export(): TargetGroupRefProps { + return { + targetGroupArn: new TargetGroupArn(new cdk.Output(this, 'TargetGroupArn', { value: this.targetGroupArn }).makeImportValue()), + defaultPort: new cdk.Output(this, 'Port', { value: this.defaultPort }).makeImportValue().toString(), + }; + } + + /** + * Add a dependency between this target group and the indicated resources + */ + public addDependency(...other: cdk.IDependable[]) { + this.resource.addDependency(...other); + } + + /** + * Register the given load balancing target as part of this group + */ + protected addLoadBalancerTarget(props: LoadBalancerTargetProps) { + if ((props.targetType === TargetType.SelfRegistering) !== (props.targetJson === undefined)) { + throw new Error('Load balancing target should specify targetJson if and only if TargetType is not SelfRegistering'); + } + if (props.targetType !== TargetType.SelfRegistering) { + if (this.targetType !== undefined && this.targetType !== props.targetType) { + throw new Error(`Already have a of type '${this.targetType}', adding '${props.targetType}'; make all targets the same type.`); + } + this.targetType = props.targetType; + } + + if (props.targetJson) { + this.targetsJson.push(props.targetJson); + } + } +} + +/** + * Properties to reference an existing target group + */ +export interface TargetGroupRefProps { + /** + * ARN of the target group + */ + targetGroupArn: TargetGroupArn; + + /** + * Port target group is listening on + */ + defaultPort: string; +} + +/** + * A target group + */ +export interface ITargetGroup { + /** + * ARN of the target group + */ + readonly targetGroupArn: TargetGroupArn; +} + +/** + * Result of attaching a target to load balancer + */ +export interface LoadBalancerTargetProps { + /** + * What kind of target this is + */ + targetType: TargetType; + + /** + * JSON representing the target's direct addition to the TargetGroup list + */ + targetJson?: any; +} diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/enums.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/enums.ts new file mode 100644 index 0000000000000..4b549051fd660 --- /dev/null +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/enums.ts @@ -0,0 +1,117 @@ +/** + * What kind of addresses to allocate to the load balancer + */ +export enum IpAddressType { + /** + * Allocate IPv4 addresses + */ + Ipv4 = 'ipv4', + + /** + * Allocate both IPv4 and IPv6 addresses + */ + DualStack = 'dualstack', +} + +/** + * Backend protocol for health checks + */ +export enum Protocol { + /** + * HTTP + */ + Http = 'HTTP', + + /** + * HTTPS + */ + Https = 'HTTPS', + + /** + * TCP + */ + Tcp = 'TCP' +} + +/** + * Load balancing protocol for application load balancers + */ +export enum ApplicationProtocol { + /** + * HTTP + */ + Http = 'HTTP', + + /** + * HTTPS + */ + Https = 'HTTPS' +} + +/** + * Elastic Load Balancing provides the following security policies for Application Load Balancers + * + * We recommend the Recommended policy for general use. You can + * use the ForwardSecrecy policy if you require Forward Secrecy + * (FS). + * + * You can use one of the TLS policies to meet compliance and security + * standards that require disabling certain TLS protocol versions, or to + * support legacy clients that require deprecated ciphers. + * + * @see https://docs.aws.amazon.com/elasticloadbalancing/latest/application/create-https-listener.html + */ +export enum SslPolicy { + /** + * The recommended security policy + */ + Recommended = 'ELBSecurityPolicy-2016-08', + + /** + * Forward secrecy ciphers only + */ + ForwardSecrecy = 'ELBSecurityPolicy-FS-2018-06', + + /** + * TLS1.2 only and no SHA ciphers + */ + TLS12 = 'ELBSecurityPolicy-TLS-1-2-2017-01', + + /** + * TLS1.2 only with all ciphers + */ + TLS12Ext = 'ELBSecurityPolicy-TLS-1-2-Ext-2018-06', + + /** + * TLS1.1 and higher with all ciphers + */ + TLS11 = 'ELBSecurityPolicy-TLS-1-1-2017-01', + + /** + * Support for DES-CBC3-SHA + * + * Do not use this security policy unless you must support a legacy client + * that requires the DES-CBC3-SHA cipher, which is a weak cipher. + */ + Legacy = 'ELBSecurityPolicy-TLS-1-0-2015-04', +} + +/** + * How to interpret the load balancing target identifiers + */ +export enum TargetType { + /** + * Targets identified by instance ID + */ + Instance = 'instance', + + /** + * Targets identified by IP address + */ + Ip = 'ip', + + /** + * A target that will register itself with the target group + */ + SelfRegistering = 'self-registering', +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/imported.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/imported.ts new file mode 100644 index 0000000000000..87f82e0a11b95 --- /dev/null +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/imported.ts @@ -0,0 +1,61 @@ +import ec2 = require('@aws-cdk/aws-ec2'); +import cdk = require('@aws-cdk/cdk'); +import { ListenerArn, LoadBalancerArn, TargetGroupArn } from "../elasticloadbalancingv2.generated"; +import { ListenerRefProps } from './base-listener'; +import { LoadBalancerRefProps } from "./base-load-balancer"; +import { TargetGroupRefProps } from './base-target-group'; + +/** + * Base class for existing load balancers + */ +export class BaseImportedLoadBalancer extends cdk.Construct { + /** + * ARN of the load balancer + */ + public readonly loadBalancerArn: LoadBalancerArn; + + /** + * VPC of the load balancer + * + * Always undefined. + */ + public readonly vpc?: ec2.VpcNetworkRef; + + constructor(parent: cdk.Construct, id: string, props: LoadBalancerRefProps) { + super(parent, id); + + this.loadBalancerArn = props.loadBalancerArn; + } +} + +/** + * Base class for existing listeners + */ +export class BaseImportedListener extends cdk.Construct { + /** + * ARN of the listener + */ + public readonly listenerArn: ListenerArn; + + constructor(parent: cdk.Construct, id: string, props: ListenerRefProps) { + super(parent, id); + + this.listenerArn = props.listenerArn; + } +} + +/** + * Base class for existing target groups + */ +export class BaseImportedTargetGroup extends cdk.Construct { + /** + * ARN of the target group + */ + public readonly targetGroupArn: TargetGroupArn; + + constructor(parent: cdk.Construct, id: string, props: TargetGroupRefProps) { + super(parent, id); + + this.targetGroupArn = props.targetGroupArn; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/load-balancer-targets.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/load-balancer-targets.ts new file mode 100644 index 0000000000000..47c83b194ae14 --- /dev/null +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/load-balancer-targets.ts @@ -0,0 +1,113 @@ +import { ApplicationTargetGroup, IApplicationLoadBalancerTarget } from "../alb/application-target-group"; +import { INetworkLoadBalancerTarget, NetworkTargetGroup } from "../nlb/network-target-group"; +import { ITargetGroup, LoadBalancerTargetProps } from "./base-target-group"; +import { TargetType } from "./enums"; + +/** + * An EC2 instance that is the target for load balancing + * + * If you register a target of this type, you are responsible for making + * sure the load balancer's security group can connect to the instance. + */ +export class InstanceTarget implements IApplicationLoadBalancerTarget, INetworkLoadBalancerTarget { + /** + * Create a new Instance target + * + * @param instanceId Instance ID of the instance to register to + * @param port Override the default port for the target group + */ + constructor(private readonly instanceId: string, private readonly port?: number) { + } + + /** + * Register this instance target with a load balancer + * + * Don't call this, it is called automatically when you add the target to a + * load balancer. + */ + public attachToApplicationTargetGroup(targetGroup: ApplicationTargetGroup): LoadBalancerTargetProps { + return this.attach(targetGroup); + } + + /** + * Register this instance target with a load balancer + * + * Don't call this, it is called automatically when you add the target to a + * load balancer. + */ + public attachToNetworkTargetGroup(targetGroup: NetworkTargetGroup): LoadBalancerTargetProps { + return this.attach(targetGroup); + } + + private attach(_targetGroup: ITargetGroup): LoadBalancerTargetProps { + return { + targetType: TargetType.Instance, + targetJson: { id: this.instanceId, port: this.port } + }; + } +} + +/** + * An IP address that is a target for load balancing. + * + * Specify IP addresses from the subnets of the virtual private cloud (VPC) for + * the target group, the RFC 1918 range (10.0.0.0/8, 172.16.0.0/12, and + * 192.168.0.0/16), and the RFC 6598 range (100.64.0.0/10). You can't specify + * publicly routable IP addresses. + * + * If you register a target of this type, you are responsible for making + * sure the load balancer's security group can send packets to the IP address. + */ +export class IpTarget implements IApplicationLoadBalancerTarget, INetworkLoadBalancerTarget { + /** + * Create a new IPAddress target + * + * The availabilityZone parameter determines whether the target receives + * traffic from the load balancer nodes in the specified Availability Zone + * or from all enabled Availability Zones for the load balancer. + * + * This parameter is not supported if the target type of the target group + * is instance. If the IP address is in a subnet of the VPC for the target + * group, the Availability Zone is automatically detected and this + * parameter is optional. If the IP address is outside the VPC, this + * parameter is required. + * + * With an Application Load Balancer, if the IP address is outside the VPC + * for the target group, the only supported value is all. + * + * Default is automatic. + * + * @param ipAddress The IP Address to load balance to + * @param port Override the group's default port + * @param availabilityZone Availability zone to send traffic from + */ + constructor(private readonly ipAddress: string, private readonly port?: number, private readonly availabilityZone?: string) { + } + + /** + * Register this instance target with a load balancer + * + * Don't call this, it is called automatically when you add the target to a + * load balancer. + */ + public attachToApplicationTargetGroup(targetGroup: ApplicationTargetGroup): LoadBalancerTargetProps { + return this.attach(targetGroup); + } + + /** + * Register this instance target with a load balancer + * + * Don't call this, it is called automatically when you add the target to a + * load balancer. + */ + public attachToNetworkTargetGroup(targetGroup: NetworkTargetGroup): LoadBalancerTargetProps { + return this.attach(targetGroup); + } + + private attach(_targetGroup: ITargetGroup): LoadBalancerTargetProps { + return { + targetType: TargetType.Ip, + targetJson: { id: this.ipAddress, port: this.port, availabilityZone: this.availabilityZone } + }; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/util.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/util.ts new file mode 100644 index 0000000000000..abe9933de4d3b --- /dev/null +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/util.ts @@ -0,0 +1,69 @@ +import { ApplicationProtocol } from "./enums"; + +export type Attributes = {[key: string]: string | undefined}; + +/** + * Render an attribute dict to a list of { key, value } pairs + */ +export function renderAttributes(attributes: Attributes) { + const ret: any[] = []; + for (const [key, value] of Object.entries(attributes)) { + if (value !== undefined) { + ret.push({ key, value }); + } + } + return ret; +} + +/** + * Return the appropriate default port for a given protocol + */ +export function defaultPortForProtocol(proto: ApplicationProtocol): number { + switch (proto) { + case ApplicationProtocol.Http: return 80; + case ApplicationProtocol.Https: return 443; + default: + throw new Error(`Unrecognized protocol: ${proto}`); + } +} + +/** + * Return the appropriate default protocol for a given port + */ +export function defaultProtocolForPort(port: number): ApplicationProtocol { + switch (port) { + case 80: + case 8000: + case 8008: + case 8080: + return ApplicationProtocol.Http; + + case 443: + case 8443: + return ApplicationProtocol.Https; + + default: + throw new Error(`Don't know default protocol for port: ${port}; please supply a protocol`); + } +} + +/** + * Given a protocol and a port, try to guess the other one if it's undefined + */ +export function determineProtocolAndPort(protocol: ApplicationProtocol | undefined, port: number | undefined): [ApplicationProtocol, number] { + if (protocol === undefined && port === undefined) { + throw new Error('Supply at least one of protocol and port'); + } + + if (protocol === undefined) { protocol = defaultProtocolForPort(port!); } + if (port === undefined) { port = defaultPortForProtocol(protocol!); } + + return [protocol, port]; +} + +/** + * Helper function to default undefined input props + */ +export function ifUndefined(x: T | undefined, def: T) { + return x !== undefined ? x : def; +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/target-group-ref.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/target-group-ref.ts deleted file mode 100644 index 1ed6f31d3f281..0000000000000 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/target-group-ref.ts +++ /dev/null @@ -1,78 +0,0 @@ -import ec2 = require('@aws-cdk/aws-ec2'); -import cdk = require('@aws-cdk/cdk'); -import { TargetGroupArn } from './elasticloadbalancingv2.generated'; -import { IListenerInternals } from './listener-ref'; - -/** - * A target group - */ -export abstract class TargetGroupRef extends cdk.Construct { - /** - * Import an existing target group - */ - public static import(parent: cdk.Construct, id: string, props: TargetGroupRefProps): TargetGroupRef { - return new ImportedTargetGroup(parent, id, props); - } - - /** - * ARN of the target group - */ - public abstract readonly targetGroupArn: TargetGroupArn; - - private readonly connectableMembers = new Array(); - private readonly listeners = new Array(); - - /** - * Export this target group - */ - public export(): TargetGroupRefProps { - return { - targetGroupArn: new TargetGroupArn(new cdk.Output(this, 'TargetGroupArn', { value: this.targetGroupArn }).makeImportValue()) - }; - } - - /** - * Register a connectable as a member of this target group - * - * The connections are created when the listener of a load balancer load - * balances to this target. - */ - public registerConnectable(connectable: ec2.IConnectable) { - this.connectableMembers.push(connectable); - for (const listener of this.listeners) { - listener.registerConnectable(connectable); - } - } - - /** - * Called when this TargetGroup is the target of a listener - */ - public bindToListener(listener: IListenerInternals): any { - for (const member of this.connectableMembers) { - listener.registerConnectable(member); - } - this.listeners.push(listener); - } -} - -/** - * Properties to reference an existing target group - */ -export interface TargetGroupRefProps { - /** - * ARN of the target group - */ - targetGroupArn: TargetGroupArn; -} - -/** - * An existing load balancer - */ -class ImportedTargetGroup extends TargetGroupRef { - public readonly targetGroupArn: TargetGroupArn; - constructor(parent: cdk.Construct, id: string, props: TargetGroupRefProps) { - super(parent, id); - - this.targetGroupArn = props.targetGroupArn; - } -} diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/target-group.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/target-group.ts deleted file mode 100644 index 567610b0f70e0..0000000000000 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/target-group.ts +++ /dev/null @@ -1,380 +0,0 @@ -import ec2 = require('@aws-cdk/aws-ec2'); -import cdk = require('@aws-cdk/cdk'); -import { cloudformation, TargetGroupArn, TargetGroupFullName, TargetGroupName } from './elasticloadbalancingv2.generated'; -import { TargetGroupRef } from './target-group-ref'; -import { Protocol } from './types'; -import { Attributes, determineProtocolAndPort, renderAttributes } from './util'; - -export interface TargetGroupProps { - /** - * The approximate number of seconds between health checks for an individual target. - * - * @default 30 - */ - healthCheckIntervalSecs?: number; - - /** - * The ping path destination where Elastic Load Balancing sends health check requests. - * - * @default / - */ - healthCheckPath?: string; - - /** - * The port that the load balancer uses when performing health checks on the targets. - * - * @default 'traffic-port' - */ - healthCheckPort?: string; - - /** - * The protocol the load balancer uses when performing health checks on targets. - * - * The TCP protocol is supported only if the protocol of the target group - * is TCP. - * - * @default HTTP for ALBs, TCP for NLBs - */ - healthCheckProtocol?: Protocol; - - /** - * The amount of time, in seconds, during which no response from a target means a failed health check. - * - * For Application Load Balancers, the range is 2–60 seconds and the - * default is 5 seconds. For Network Load Balancers, this is 10 seconds for - * TCP and HTTPS health checks and 6 seconds for HTTP health checks. - * - * @default 5 for ALBs, 10 or 6 for NLBs - */ - healthCheckTimeoutSeconds?: number; - - /** - * The number of consecutive health checks successes required before considering an unhealthy target healthy. - * - * For Application Load Balancers, the default is 5. For Network Load Balancers, the default is 3. - * - * @default 5 for ALBs, 3 for NLBs - */ - healthyThresholdCount?: number; - - /** - * The number of consecutive health check failures required before considering a target unhealthy. - * - * For Application Load Balancers, the default is 2. For Network Load - * Balancers, this value must be the same as the healthy threshold count. - * - * @default 2 - */ - unhealthyThresholdCount?: number; - - /** - * HTTP code to use when checking for a successful response from a target. - * - * For Application Load Balancers, you can specify values between 200 and - * 499, and the default value is 200. You can specify multiple values (for - * example, "200,202") or a range of values (for example, "200-299"). - */ - healthyHttpCodes?: string; - - /** - * The name of the target group. - * - * This name must be unique per region per account, can have a maximum of - * 32 characters, must contain only alphanumeric characters or hyphens, and - * must not begin or end with a hyphen. - * - * @default Automatically generated - */ - targetGroupName?: string, - - /** - * The port on which the targets receive traffic. - * - * This port is used unless you specify a port override when registering the target. - * - * @default Determined from Protocol if known. - */ - port?: number; - - /** - * The protocol to use for routing traffic to the targets. - * - * For Application Load Balancers, the supported protocols are HTTP and - * HTTPS. For Network Load Balancers, the supported protocol is TCP. - * - * @default Determined from Port if known. - */ - protocol?: Protocol; - - /** - * The targets to add to this target group. - * - * Can be `Instance`, `IPAddress`, or any self-registering load balancing - * target. If you use either `Instance` or `IPAddress` as targets, all - * target must be of the same type. - */ - targets?: ILoadBalancerTarget[]; - - /** - * The virtual private cloud (VPC). - */ - vpc: ec2.VpcNetwork; - - /** - * The amount of time for Elastic Load Balancing to wait before deregistering a target. - * - * The range is 0–3600 seconds. - * - * @default 300 - */ - deregistrationDelaySec?: number; - - /** - * The time period during which the load balancer sends a newly registered - * target a linearly increasing share of the traffic to the target group. - * - * The range is 30–900 seconds (15 minutes). - * - * @default 0 - */ - slowStartSec?: number; - - /** - * The stickiness cookie expiration period. - * - * Setting this value enables load balancer stickiness. - * - * After this period, the cookie is considered stale. The minimum value is - * 1 second and the maximum value is 7 days (604800 seconds). - * - * @default 86400 (1 day) - */ - stickinessCookieDurationSec?: number; -} - -/** - * Define the target of a load balancer - */ -export class TargetGroup extends TargetGroupRef { - public readonly targetGroupArn: TargetGroupArn; - public readonly targetGroupFullName: TargetGroupFullName; - public readonly targetGroupName: TargetGroupName; - - private readonly attributes: Attributes = {}; - private readonly targetsJson = new Array(); - private targetType?: TargetType; - - constructor(parent: cdk.Construct, id: string, props: TargetGroupProps) { - super(parent, id); - - if (props.deregistrationDelaySec !== undefined) { - this.setAttribute('deregistration_delay.timeout_seconds', props.deregistrationDelaySec.toString()); - } - if (props.slowStartSec !== undefined) { - this.setAttribute('slow_start.duration_seconds', props.slowStartSec.toString()); - } - if (props.stickinessCookieDurationSec !== undefined) { - this.setAttribute('stickiness.enabled', 'true'); - this.setAttribute('stickiness.type', 'lb_cookie'); - this.setAttribute('stickiness.lb_cookie.duration_seconds', props.stickinessCookieDurationSec.toString()); - } - - const [protocol, port] = determineProtocolAndPort(props.protocol, props.port); - - const resource = new cloudformation.TargetGroupResource(this, 'Resource', { - targetGroupName: props.targetGroupName, - protocol, - port, - targetGroupAttributes: new cdk.Token(() => renderAttributes(this.attributes)), - targetType: new cdk.Token(() => this.targetType), - targets: new cdk.Token(() => this.targetsJson), - vpcId: props.vpc.vpcId, - - // HEALTH CHECK - healthCheckIntervalSeconds: props.healthCheckIntervalSecs, - healthCheckPath: props.healthCheckPath, - healthCheckPort: props.healthCheckPort, - healthCheckProtocol: props.healthCheckProtocol, - healthCheckTimeoutSeconds: props.healthCheckTimeoutSeconds, - healthyThresholdCount: props.healthyThresholdCount, - matcher: props.healthyHttpCodes === undefined ? undefined : { - httpCode: props.healthyHttpCodes - }, - }); - - this.targetGroupArn = resource.ref; - this.targetGroupFullName = resource.targetGroupFullName; - this.targetGroupName = resource.targetGroupName; - - (props.targets || []).forEach(this.addTarget.bind(this)); - } - - /** - * Set a non-standard attribute on the target group - * - * @see https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-target-groups.html#target-group-attributes - */ - public setAttribute(key: string, value: string | undefined) { - this.attributes[key] = value; - } - - /** - * Register the given load balancing target as part of this group - */ - public addTarget(target: ILoadBalancerTarget) { - const ret = target.attachToELBv2TargetGroup(this); - if ((ret.targetType === TargetType.SelfRegistering) !== (ret.targetJson === undefined)) { - throw new Error('Load balancing target should specify targetJson if and only if TargetType is not SelfRegistering'); - } - if (ret.targetType !== TargetType.SelfRegistering) { - if (this.targetType !== undefined && this.targetType !== ret.targetType) { - throw new Error(`Already have a of type '${this.targetType}', adding '${ret.targetType}'; make all targets the same type.`); - } - this.targetType = ret.targetType; - } - - if (ret.targetJson) { - this.targetsJson.push(ret.targetJson); - } - } -} - -/** - * How to interpret the load balancing target identifiers - */ -export enum TargetType { - /** - * Targets identified by instance ID - */ - Instance = 'instance', - - /** - * Targets identified by IP address - */ - Ip = 'ip', - - /** - * A target that will register itself with the target group - */ - SelfRegistering = 'self-registering', -} - -/** - * Interface that is going to be implemented by constructs that you can load balance to - */ -export interface ILoadBalancerTarget { - /** - * Attach load-balanced target to a TargetGroup - * - * May return JSON to directly add to the [Targets] list, or return undefined - * if the target will register itself with the load balancer. - */ - attachToELBv2TargetGroup(targetGroup: TargetGroupRef): LoadBalancerTargetProps; -} - -/** - * Result of attaching a target to load balancer - */ -export interface LoadBalancerTargetProps { - /** - * What kind of target this is - */ - targetType: TargetType; - - /** - * JSON representing the target's direct addition to the TargetGroup list - */ - targetJson?: any; -} - -/** - * Properties for a load balancer target - */ -export interface TargetProps { - /** - * @default Automatic - */ - availabilityZone?: string; - - /** - * The ID of the target. - * - * If the target type of the target group is instance, specify an instance - * ID. If the target type is ip, specify an IP address. - */ - id: string; - - /** - * Override the default port on which the target is listening - */ - port?: number; -} - -/** - * An EC2 instance that is the target for load balancing - * - * If you register a target of this type, you are responsible for making - * sure the load balancer's security group can connect to the instance. - */ -export class Instance implements ILoadBalancerTarget { - /** - * Create a new Instance target - * - * @param instanceId Instance ID of the instance to register to - * @param port Override the default port for the target group - */ - constructor(private readonly instanceId: string, private readonly port?: number) { - } - - public attachToELBv2TargetGroup(_targetGroup: TargetGroupRef): LoadBalancerTargetProps { - return { - targetType: TargetType.Instance, - targetJson: { id: this.instanceId, port: this.port } - }; - } -} - -/** - * An IP address that is a target for load balancing. - * - * Specify IP addresses from the subnets of the virtual private cloud (VPC) for - * the target group, the RFC 1918 range (10.0.0.0/8, 172.16.0.0/12, and - * 192.168.0.0/16), and the RFC 6598 range (100.64.0.0/10). You can't specify - * publicly routable IP addresses. - * - * If you register a target of this type, you are responsible for making - * sure the load balancer's security group can send packets to the IP address. - */ -export class IPAddress implements ILoadBalancerTarget { - /** - * Create a new IPAddress target - * - * The availabilityZone parameter determines whether the target receives - * traffic from the load balancer nodes in the specified Availability Zone - * or from all enabled Availability Zones for the load balancer. - * - * This parameter is not supported if the target type of the target group - * is instance. If the IP address is in a subnet of the VPC for the target - * group, the Availability Zone is automatically detected and this - * parameter is optional. If the IP address is outside the VPC, this - * parameter is required. - * - * With an Application Load Balancer, if the IP address is outside the VPC - * for the target group, the only supported value is all. - * - * Default is automatic. - * - * @param ipAddress The IP Address to load balance to - * @param port Override the group's default port - * @param availabilityZone Availability zone to send traffic from - */ - constructor(private readonly ipAddress: string, private readonly port?: number, private readonly availabilityZone?: string) { - } - - public attachToELBv2TargetGroup(_targetGroup: TargetGroupRef): LoadBalancerTargetProps { - return { - targetType: TargetType.Ip, - targetJson: { id: this.ipAddress, port: this.port, availabilityZone: this.availabilityZone } - }; - } -} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/types.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/types.ts deleted file mode 100644 index ac75ee4548946..0000000000000 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/types.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Load balancing protocol - */ -export enum Protocol { - /** - * TCP - */ - Tcp = 'TCP', - - /** - * HTTP - */ - Http = 'HTTP', - - /** - * HTTPS - */ - Https = 'HTTPS' -} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/util.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/util.ts deleted file mode 100644 index 536b3372dfbff..0000000000000 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/util.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Protocol } from "./types"; - -export type Attributes = {[key: string]: string | undefined}; -export function renderAttributes(attributes: Attributes) { - const ret: any = {}; - for (const [key, value] of Object.entries(attributes)) { - if (value !== undefined) { - ret.push({ key, value }); - } - } - return ret; -} - -export function defaultPortForProtocol(proto: Protocol): number { - switch (proto) { - case Protocol.Http: return 80; - case Protocol.Https: return 443; - case Protocol.Tcp: throw new Error("Can't determine default port for protocol Tcp; please supply a port"); - default: - throw new Error(`Unrecognized protocol: ${proto}`); - } -} - -export function defaultProtocolForPort(port: number): Protocol { - switch (port) { - case 80: - case 8080: - case 8008: - return Protocol.Http; - - case 443: - case 8443: - return Protocol.Https; - - default: - throw new Error(`Don't know default protocol for port: ${port}; please supply a protocol`); - } -} - -export function determineProtocolAndPort(protocol: Protocol | undefined, port: number | undefined): [Protocol, number] { - if (protocol === undefined && port === undefined) { - throw new Error('Supply at least one of protocol and port'); - } - - if (protocol === undefined) { protocol = defaultProtocolForPort(port!); } - if (port === undefined) { port = defaultPortForProtocol(protocol!); } - - return [protocol, port]; -} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json b/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json index e2779a740fde2..21e4a9e35e78c 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json @@ -55,7 +55,8 @@ "@aws-cdk/assert": "^0.9.1", "cdk-build-tools": "^0.9.1", "cfn2ts": "^0.9.1", - "pkglint": "^0.9.1" + "pkglint": "^0.9.1", + "cdk-integ-tools": "^0.9.1" }, "dependencies": { "@aws-cdk/cdk": "^0.9.1", diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.listener.ts new file mode 100644 index 0000000000000..becde4d316f09 --- /dev/null +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.listener.ts @@ -0,0 +1,305 @@ +import { expect, haveResource } from '@aws-cdk/assert'; +import ec2 = require('@aws-cdk/aws-ec2'); +import cdk = require('@aws-cdk/cdk'); +import { Test } from 'nodeunit'; +import elbv2 = require('../../lib'); +import { FakeSelfRegisteringTarget } from '../helpers'; + +export = { + 'Listener guesses protocol from port'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'Stack'); + const lb = new elbv2.ApplicationLoadBalancer(stack, 'LB', { vpc }); + + // WHEN + lb.addListener('Listener', { + port: 443, + certificateArns: [new cdk.Arn('')], + defaultTargetGroups: [new elbv2.ApplicationTargetGroup(stack, 'Group', { vpc, port: 80 })] + }); + + // THEN + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::Listener', { + Protocol: 'HTTPS' + })); + + test.done(); + }, + + 'Listener guesses port from protocol'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'Stack'); + const lb = new elbv2.ApplicationLoadBalancer(stack, 'LB', { vpc }); + + // WHEN + lb.addListener('Listener', { + protocol: elbv2.ApplicationProtocol.Http, + defaultTargetGroups: [new elbv2.ApplicationTargetGroup(stack, 'Group', { vpc, port: 80 })] + }); + + // THEN + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::Listener', { + Port: 80 + })); + + test.done(); + }, + + 'HTTPS listener requires certificate'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'Stack'); + const lb = new elbv2.ApplicationLoadBalancer(stack, 'LB', { vpc }); + + // WHEN + lb.addListener('Listener', { + port: 443, + defaultTargetGroups: [new elbv2.ApplicationTargetGroup(stack, 'Group', { vpc, port: 80 })] + }); + + // THEN + const errors = stack.validateTree(); + test.deepEqual(errors.map(e => e.message), ['HTTPS Listener needs at least one certificate (call addCertificateArns)']); + + test.done(); + }, + + 'Can add target groups with and without conditions'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'Stack'); + const lb = new elbv2.ApplicationLoadBalancer(stack, 'LB', { vpc }); + const listener = lb.addListener('Listener', { port: 443 }); + const group = new elbv2.ApplicationTargetGroup(stack, 'TargetGroup', { vpc, port: 80 }); + + // WHEN + listener.addTargetGroups('Default', { + targetGroups: [group] + }); + listener.addTargetGroups('WithPath', { + priority: 10, + pathPattern: '/hello', + targetGroups: [group] + }); + + // THEN + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::Listener', { + DefaultActions: [ + { + TargetGroupArn: { Ref: "TargetGroup3D7CD9B8" }, + Type: "forward" + } + ], + })); + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::ListenerRule', { + Priority: 10, + Conditions: [ + { + Field: 'path-pattern', + Values: ['/hello'] + } + ], + Actions: [ + { + TargetGroupArn: { Ref: "TargetGroup3D7CD9B8" }, + Type: "forward" + } + ], + })); + + test.done(); + }, + + 'Can implicitly create target groups with and without conditions'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'Stack'); + const lb = new elbv2.ApplicationLoadBalancer(stack, 'LB', { vpc }); + const listener = lb.addListener('Listener', { port: 443 }); + + // WHEN + listener.addTargets('Targets', { + port: 80, + targets: [new elbv2.InstanceTarget('i-12345')] + }); + listener.addTargets('WithPath', { + priority: 10, + pathPattern: '/hello', + port: 80, + targets: [new elbv2.InstanceTarget('i-5678')] + }); + + // THEN + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::Listener', { + DefaultActions: [ + { + TargetGroupArn: { Ref: "LBListenerTargetsGroup76EF81E8" }, + Type: "forward" + } + ], + })); + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::TargetGroup', { + VpcId: { Ref: "Stack8A423254" }, + Port: 80, + Protocol: "HTTP", + Targets: [ + { Id: "i-12345" } + ] + })); + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::ListenerRule', { + Actions: [ + { + TargetGroupArn: { Ref: "LBListenerWithPathGroupE889F9E5" }, + Type: "forward" + } + ], + })); + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::TargetGroup', { + VpcId: { Ref: "Stack8A423254" }, + Port: 80, + Protocol: "HTTP", + Targets: [ + { Id: "i-5678" } + ] + })); + + test.done(); + }, + + 'Add certificate to constructed listener'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'Stack'); + const lb = new elbv2.ApplicationLoadBalancer(stack, 'LB', { vpc }); + const listener = lb.addListener('Listener', { port: 443 }); + + // WHEN + listener.addCertificateArns('Arns', new cdk.Arn('cert')); + + // THEN + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::Listener', { + Certificates: [ + { CertificateArn: "cert" } + ], + })); + + test.done(); + }, + + 'Add certificate to imported listener'(test: Test) { + // GIVEN + const stack1 = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack1, 'Stack'); + const lb = new elbv2.ApplicationLoadBalancer(stack1, 'LB', { vpc }); + const listener1 = lb.addListener('Listener', { port: 443 }); + + const stack2 = new cdk.Stack(); + const listener2 = elbv2.ApplicationListener.import(stack2, 'Listener', listener1.export()); + + // WHEN + listener2.addCertificateArns('Arns', new cdk.Arn('cert')); + + // THEN + expect(stack2).to(haveResource('AWS::ElasticLoadBalancingV2::ListenerCertificate', { + Certificates: [ + { CertificateArn: "cert" } + ], + })); + + test.done(); + }, + + 'Enable stickiness for targets'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'Stack'); + const lb = new elbv2.ApplicationLoadBalancer(stack, 'LB', { vpc }); + const listener = lb.addListener('Listener', { port: 443 }); + + // WHEN + const group = listener.addTargets('Group', { + port: 80, + targets: [new FakeSelfRegisteringTarget(stack, 'Target', vpc)] + }); + group.enableCookieStickiness(3600); + + // THEN + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::TargetGroup', { + TargetGroupAttributes: [ + { + Key: "stickiness.enabled", + Value: "true" + }, + { + Key: "stickiness.type", + Value: "lb_cookie" + }, + { + Key: "stickiness.lb_cookie.duration_seconds", + Value: "3600" + } + ] + })); + + test.done(); + }, + + 'Enable health check for tagets targets'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'Stack'); + const lb = new elbv2.ApplicationLoadBalancer(stack, 'LB', { vpc }); + const listener = lb.addListener('Listener', { port: 443 }); + + // WHEN + const group = listener.addTargets('Group', { + port: 80, + targets: [new FakeSelfRegisteringTarget(stack, 'Target', vpc)] + }); + group.configureHealthCheck({ + timeoutSeconds: 3600, + intervalSecs: 30, + path: '/test', + }); + + // THEN + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::TargetGroup', { + HealthCheckIntervalSeconds: 30, + HealthCheckPath: "/test", + HealthCheckTimeoutSeconds: 3600, + })); + + test.done(); + }, + + 'Can call addTargetGroups on imported listener'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'VPC'); + const listener = elbv2.ApplicationListener.import(stack, 'Listener', { listenerArn: new elbv2.ListenerArn('ieks') }); + const group = new elbv2.ApplicationTargetGroup(stack, 'TargetGroup', { vpc, port: 80 }); + + // WHEN + listener.addTargetGroups('Gruuup', { + priority: 30, + hostHeader: 'example.com', + targetGroups: [group] + }); + + // THEN + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::ListenerRule', { + ListenerArn: 'ieks', + Priority: 30, + Actions: [ + { + TargetGroupArn: { Ref: "TargetGroup3D7CD9B8" }, + Type: "forward" + } + ], + })); + + test.done(); + } +}; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.load-balancer.ts new file mode 100644 index 0000000000000..15cc702a1c1a1 --- /dev/null +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.load-balancer.ts @@ -0,0 +1,127 @@ +import { expect, haveResource } from '@aws-cdk/assert'; +import ec2 = require('@aws-cdk/aws-ec2'); +import s3 = require('@aws-cdk/aws-s3'); +import cdk = require('@aws-cdk/cdk'); +import { Test } from 'nodeunit'; +import elbv2 = require('../../lib'); + +export = { + 'Trivial construction: internet facing'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'Stack'); + + // WHEN + new elbv2.ApplicationLoadBalancer(stack, 'LB', { + vpc, + internetFacing: true, + }); + + // THEN + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::LoadBalancer', { + Scheme: "internet-facing", + Subnets: [ + { Ref: "StackPublicSubnet1Subnet0AD81D22" }, + { Ref: "StackPublicSubnet2Subnet3C7D2288" }, + { Ref: "StackPublicSubnet3SubnetCC1055D9" } + ], + Type: "application" + })); + + test.done(); + }, + + 'Trivial construction: internal'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'Stack'); + + // WHEN + new elbv2.ApplicationLoadBalancer(stack, 'LB', { vpc }); + + // THEN + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::LoadBalancer', { + Scheme: "internal", + Subnets: [ + { Ref: "StackPrivateSubnet1Subnet47AC2BC7" }, + { Ref: "StackPrivateSubnet2SubnetA2F8EDD8" }, + { Ref: "StackPrivateSubnet3Subnet28548F2E" } + ], + Type: "application" + })); + + test.done(); + }, + + 'Attributes'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'Stack'); + + // WHEN + new elbv2.ApplicationLoadBalancer(stack, 'LB', { + vpc, + deletionProtection: true, + http2Enabled: false, + idleTimeoutSecs: 1000, + }); + + // THEN + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::LoadBalancer', { + LoadBalancerAttributes: [ + { + Key: "deletion_protection.enabled", + Value: "true" + }, + { + Key: "routing.http2.enabled", + Value: "false" + }, + { + Key: "idle_timeout.timeout_seconds", + Value: "1000" + } + ] + })); + + test.done(); + }, + + 'Access logging'(test: Test) { + // GIVEN + const stack = new cdk.Stack(undefined, undefined, { env: { region: 'us-east-1' }}); + const vpc = new ec2.VpcNetwork(stack, 'Stack'); + const bucket = new s3.Bucket(stack, 'AccessLoggingBucket'); + const lb = new elbv2.ApplicationLoadBalancer(stack, 'LB', { vpc }); + + // WHEN + lb.logAccessLogs(bucket); + + // THEN + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::LoadBalancer', { + LoadBalancerAttributes: [ + { + Key: "access_logs.s3.enabled", + Value: "true" + }, + { + Key: "access_logs.s3.bucket", + Value: { Ref: "AccessLoggingBucketA6D88F29" } + } + ], + })); + expect(stack).to(haveResource('AWS::S3::BucketPolicy', { + PolicyDocument: { + Statement: [ + { + Action: "s3:PutObject", + Principal: { AWS: { "Fn::Join": [ "", [ "arn:", { Ref: "AWS::Partition" }, ":iam::", "127311923021", ":root" ] ] } }, + Resource: { "Fn::Join": [ "", [ { "Fn::GetAtt": [ "AccessLoggingBucketA6D88F29", "Arn" ] }, "/", "", "*" ] ] } + } + ] + } + })); + + test.done(); + } +}; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.security-groups.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.security-groups.ts new file mode 100644 index 0000000000000..90aebfdcb0161 --- /dev/null +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.security-groups.ts @@ -0,0 +1,135 @@ +import { expect, haveResource } from '@aws-cdk/assert'; +import ec2 = require('@aws-cdk/aws-ec2'); +import cdk = require('@aws-cdk/cdk'); +import { Test } from 'nodeunit'; +import elbv2 = require('../../lib'); +import { FakeSelfRegisteringTarget } from '../helpers'; + +export = { + 'security groups are automatically opened bidi for default rule'(test: Test) { + // GIVEN + const fixture = new TestFixture(); + const target = new FakeSelfRegisteringTarget(fixture.stack, 'Target', fixture.vpc); + + // WHEN + fixture.listener.addTargets('TargetGroup', { + port: 8008, + targets: [target] + }); + + // THEN + expectStandardBidi(fixture.stack); + + test.done(); + }, + + 'security groups are automatically opened bidi for additional rule'(test: Test) { + // GIVEN + const fixture = new TestFixture(); + const target1 = new FakeSelfRegisteringTarget(fixture.stack, 'DefaultTarget', fixture.vpc); + const target2 = new FakeSelfRegisteringTarget(fixture.stack, 'Target', fixture.vpc); + + // WHEN + fixture.listener.addTargets('TargetGroup1', { + port: 80, + targets: [target1] + }); + + fixture.listener.addTargetGroups('Rule', { + priority: 10, + hostHeader: 'example.com', + targetGroups: [new elbv2.ApplicationTargetGroup(fixture.stack, 'TargetGroup2', { + vpc: fixture.vpc, + port: 8008, + targets: [target2] + })] + }); + + // THEN + expectStandardBidi(fixture.stack); + + test.done(); + }, + + 'adding the same targets twice also works'(test: Test) { + // GIVEN + const fixture = new TestFixture(); + const target = new FakeSelfRegisteringTarget(fixture.stack, 'Target', fixture.vpc); + + // WHEN + const group = new elbv2.ApplicationTargetGroup(fixture.stack, 'TargetGroup', { + vpc: fixture.vpc, + port: 8008, + targets: [target] + }); + + fixture.listener.addTargetGroups('Default', { + targetGroups: [group] + }); + fixture.listener.addTargetGroups('WithPath', { + priority: 10, + pathPattern: '/hello', + targetGroups: [group] + }); + + // THEN + expectStandardBidi(fixture.stack); + + test.done(); + }, + + 'same result if target is added to group after assigning to listener'(test: Test) { + // GIVEN + const fixture = new TestFixture(); + const group = new elbv2.ApplicationTargetGroup(fixture.stack, 'TargetGroup', { + vpc: fixture.vpc, + port: 8008 + }); + fixture.listener.addTargetGroups('Default', { + targetGroups: [group] + }); + + // WHEN + const target = new FakeSelfRegisteringTarget(fixture.stack, 'Target', fixture.vpc); + group.addTarget(target); + + // THEN + expectStandardBidi(fixture.stack); + + test.done(); + }, +}; + +function expectStandardBidi(stack: cdk.Stack) { + expect(stack).to(haveResource('AWS::EC2::SecurityGroupEgress', { + IpProtocol: "tcp", + Description: "Load balancer to target", + DestinationSecurityGroupId: { "Fn::GetAtt": [ "TargetSGDB98152D", "GroupId" ] }, + FromPort: 8008, + ToPort: 8008 + })); + expect(stack).to(haveResource('AWS::EC2::SecurityGroupIngress', { + IpProtocol: "tcp", + Description: "Load balancer to target", + FromPort: 8008, + GroupId: { "Fn::GetAtt": [ "TargetSGDB98152D", "GroupId" ] }, + SourceSecurityGroupId: { "Fn::GetAtt": [ "LBSecurityGroup8A41EA2B", "GroupId" ] }, + ToPort: 8008 + })); +} + +class TestFixture { + public readonly stack: cdk.Stack; + public readonly vpc: ec2.VpcNetwork; + public readonly lb: elbv2.ApplicationLoadBalancer; + public readonly listener: elbv2.ApplicationListener; + + constructor() { + this.stack = new cdk.Stack(); + this.vpc = new ec2.VpcNetwork(this.stack, 'VPC', { + maxAZs: 2 + }); + this.lb = new elbv2.ApplicationLoadBalancer(this.stack, 'LB', { vpc: this.vpc }); + this.listener = this.lb.addListener('Listener', { port: 80 }); + } +} diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/helpers.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/helpers.ts new file mode 100644 index 0000000000000..5f818262f9f33 --- /dev/null +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/helpers.ts @@ -0,0 +1,26 @@ +import ec2 = require('@aws-cdk/aws-ec2'); +import cdk = require('@aws-cdk/cdk'); +import elbv2 = require('../lib'); + +export class FakeSelfRegisteringTarget extends cdk.Construct implements elbv2.IApplicationLoadBalancerTarget, elbv2.INetworkLoadBalancerTarget, + ec2.IConnectable { + public readonly securityGroup: ec2.SecurityGroup; + public readonly connections: ec2.Connections; + + constructor(parent: cdk.Construct, id: string, vpc: ec2.VpcNetwork) { + super(parent, id); + this.securityGroup = new ec2.SecurityGroup(this, 'SG', { vpc }); + this.connections = new ec2.Connections({ + securityGroup: this.securityGroup + }); + } + + public attachToApplicationTargetGroup(targetGroup: elbv2.ApplicationTargetGroup): elbv2.LoadBalancerTargetProps { + targetGroup.registerConnectable(this); + return { targetType: elbv2.TargetType.SelfRegistering }; + } + + public attachToNetworkTargetGroup(_targetGroup: elbv2.NetworkTargetGroup): elbv2.LoadBalancerTargetProps { + return { targetType: elbv2.TargetType.SelfRegistering }; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.alb.expected.json b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.alb.expected.json new file mode 100644 index 0000000000000..07eeab7b440b4 --- /dev/null +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.alb.expected.json @@ -0,0 +1,430 @@ +{ + "Resources": { + "VPCB9E5F0B4": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-elbv2-integ/VPC" + } + ] + } + }, + "VPCPublicSubnet1SubnetB4246D30": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/18", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-elbv2-integ/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1RouteTableFEE4B781": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-elbv2-integ/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1RouteTableAssociatioin249B4093": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + } + } + }, + "VPCPublicSubnet1EIP6AD938E8": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc" + } + }, + "VPCPublicSubnet1NATGatewayE0556630": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet1EIP6AD938E8", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-elbv2-integ/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1DefaultRoute91CEF279": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, + "VPCPublicSubnet2Subnet74179F39": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.64.0/18", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-elbv2-integ/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet2RouteTable6F1A15F1": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-elbv2-integ/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet2RouteTableAssociatioin766225D7": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + } + } + }, + "VPCPublicSubnet2EIP4947BC00": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc" + } + }, + "VPCPublicSubnet2NATGateway3C070193": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet2EIP4947BC00", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-elbv2-integ/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet2DefaultRouteB7481BBA": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, + "VPCPrivateSubnet1Subnet8BCA10E0": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/18", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-elbv2-integ/VPC/PrivateSubnet1" + } + ] + } + }, + "VPCPrivateSubnet1RouteTableBE8A6027": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-elbv2-integ/VPC/PrivateSubnet1" + } + ] + } + }, + "VPCPrivateSubnet1RouteTableAssociatioin77F7CA18": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + } + } + }, + "VPCPrivateSubnet1DefaultRouteAE1D6490": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet1NATGatewayE0556630" + } + } + }, + "VPCPrivateSubnet2SubnetCFCDAA7A": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.192.0/18", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-elbv2-integ/VPC/PrivateSubnet2" + } + ] + } + }, + "VPCPrivateSubnet2RouteTable0A19E10E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-elbv2-integ/VPC/PrivateSubnet2" + } + ] + } + }, + "VPCPrivateSubnet2RouteTableAssociatioinC31995B4": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + } + } + }, + "VPCPrivateSubnet2DefaultRouteF4F5CFD2": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet2NATGateway3C070193" + } + } + }, + "VPCIGWB7E252D3": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-elbv2-integ/VPC" + } + ] + } + }, + "VPCVPCGW99B986DC": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "InternetGatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, + "LB8A12904C": { + "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", + "Properties": { + "LoadBalancerAttributes": [], + "Scheme": "internet-facing", + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "LBSecurityGroup8A41EA2B", + "GroupId" + ] + } + ], + "Subnets": [ + { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, + { + "Ref": "VPCPublicSubnet2Subnet74179F39" + } + ], + "Type": "application" + } + }, + "LBSecurityGroup8A41EA2B": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "Automatically created Security Group for ELB awscdkelbv2integLB9950B1E4", + "SecurityGroupEgress": [], + "SecurityGroupIngress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Open to the world", + "FromPort": 80, + "IpProtocol": "tcp", + "ToPort": 80 + } + ], + "VpcId": { + "Ref": "VPCB9E5F0B4" + } + } + }, + "LBListener49E825B4": { + "Type": "AWS::ElasticLoadBalancingV2::Listener", + "Properties": { + "DefaultActions": [ + { + "TargetGroupArn": { + "Ref": "LBListenerTargetGroupF04FCF6D" + }, + "Type": "forward" + } + ], + "LoadBalancerArn": { + "Ref": "LB8A12904C" + }, + "Port": 80, + "Protocol": "HTTP", + "Certificates": [] + } + }, + "LBListenerTargetGroupF04FCF6D": { + "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", + "Properties": { + "Port": 80, + "Protocol": "HTTP", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "TargetGroupAttributes": [], + "Targets": [ + { + "Id": "10.0.1.1" + } + ], + "TargetType": "ip" + } + }, + "LBListenerConditionalTargetGroupA75CCCD9": { + "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", + "Properties": { + "Port": 80, + "Protocol": "HTTP", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "TargetGroupAttributes": [], + "Targets": [ + { + "Id": "10.0.1.2" + } + ], + "TargetType": "ip" + } + }, + "LBListenerConditionalTargetRule91FA260F": { + "Type": "AWS::ElasticLoadBalancingV2::ListenerRule", + "Properties": { + "Actions": [ + { + "TargetGroupArn": { + "Ref": "LBListenerConditionalTargetGroupA75CCCD9" + }, + "Type": "forward" + } + ], + "Conditions": [ + { + "Field": "host-header", + "Values": [ + "example.com" + ] + } + ], + "ListenerArn": { + "Ref": "LBListener49E825B4" + }, + "Priority": 10 + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.alb.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.alb.ts new file mode 100644 index 0000000000000..4b10506ac0ea9 --- /dev/null +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.alb.ts @@ -0,0 +1,36 @@ +#!/usr/bin/env node +import ec2 = require('@aws-cdk/aws-ec2'); +import cdk = require('@aws-cdk/cdk'); +import elbv2 = require('../lib'); + +const app = new cdk.App(process.argv); +const stack = new cdk.Stack(app, 'aws-cdk-elbv2-integ'); + +const vpc = new ec2.VpcNetwork(stack, 'VPC', { + maxAZs: 2 +}); + +const lb = new elbv2.ApplicationLoadBalancer(stack, 'LB', { + vpc, + internetFacing: true +}); + +const listener = lb.addListener('Listener', { + port: 80, +}); + +listener.addTargets('Target', { + port: 80, + targets: [new elbv2.IpTarget('10.0.1.1')] +}); + +listener.addTargets('ConditionalTarget', { + priority: 10, + hostHeader: 'example.com', + port: 80, + targets: [new elbv2.IpTarget('10.0.1.2')] +}); + +listener.connections.allowDefaultPortFromAnyIpv4('Open to the world'); + +process.stdout.write(app.run()); diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.nlb.expected.json b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.nlb.expected.json new file mode 100644 index 0000000000000..6c7d92a1e87fe --- /dev/null +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.nlb.expected.json @@ -0,0 +1,365 @@ +{ + "Resources": { + "VPCB9E5F0B4": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-elbv2-integ/VPC" + } + ] + } + }, + "VPCPublicSubnet1SubnetB4246D30": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/18", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-elbv2-integ/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1RouteTableFEE4B781": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-elbv2-integ/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1RouteTableAssociatioin249B4093": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + } + } + }, + "VPCPublicSubnet1EIP6AD938E8": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc" + } + }, + "VPCPublicSubnet1NATGatewayE0556630": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet1EIP6AD938E8", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-elbv2-integ/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1DefaultRoute91CEF279": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, + "VPCPublicSubnet2Subnet74179F39": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.64.0/18", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-elbv2-integ/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet2RouteTable6F1A15F1": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-elbv2-integ/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet2RouteTableAssociatioin766225D7": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + } + } + }, + "VPCPublicSubnet2EIP4947BC00": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc" + } + }, + "VPCPublicSubnet2NATGateway3C070193": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet2EIP4947BC00", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-elbv2-integ/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet2DefaultRouteB7481BBA": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, + "VPCPrivateSubnet1Subnet8BCA10E0": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/18", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-elbv2-integ/VPC/PrivateSubnet1" + } + ] + } + }, + "VPCPrivateSubnet1RouteTableBE8A6027": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-elbv2-integ/VPC/PrivateSubnet1" + } + ] + } + }, + "VPCPrivateSubnet1RouteTableAssociatioin77F7CA18": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + } + } + }, + "VPCPrivateSubnet1DefaultRouteAE1D6490": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet1NATGatewayE0556630" + } + } + }, + "VPCPrivateSubnet2SubnetCFCDAA7A": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.192.0/18", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-elbv2-integ/VPC/PrivateSubnet2" + } + ] + } + }, + "VPCPrivateSubnet2RouteTable0A19E10E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-elbv2-integ/VPC/PrivateSubnet2" + } + ] + } + }, + "VPCPrivateSubnet2RouteTableAssociatioinC31995B4": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + } + } + }, + "VPCPrivateSubnet2DefaultRouteF4F5CFD2": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet2NATGateway3C070193" + } + } + }, + "VPCIGWB7E252D3": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-elbv2-integ/VPC" + } + ] + } + }, + "VPCVPCGW99B986DC": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "InternetGatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, + "LB8A12904C": { + "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", + "Properties": { + "LoadBalancerAttributes": [], + "Scheme": "internet-facing", + "Subnets": [ + { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, + { + "Ref": "VPCPublicSubnet2Subnet74179F39" + } + ], + "Type": "network" + } + }, + "LBListener49E825B4": { + "Type": "AWS::ElasticLoadBalancingV2::Listener", + "Properties": { + "DefaultActions": [ + { + "TargetGroupArn": { + "Ref": "LBListenerTargetGroupF04FCF6D" + }, + "Type": "forward" + } + ], + "LoadBalancerArn": { + "Ref": "LB8A12904C" + }, + "Port": 443, + "Protocol": "TCP" + } + }, + "LBListenerTargetGroupF04FCF6D": { + "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", + "Properties": { + "Port": 443, + "Protocol": "TCP", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "TargetGroupAttributes": [], + "Targets": [ + { + "Id": "10.0.1.1" + } + ], + "TargetType": "ip" + }, + "DependsOn": [ + "VPCB9E5F0B4", + "VPCIGWB7E252D3", + "VPCVPCGW99B986DC" + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.nlb.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.nlb.ts new file mode 100644 index 0000000000000..4f6520f69ccaf --- /dev/null +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.nlb.ts @@ -0,0 +1,31 @@ +#!/usr/bin/env node +import ec2 = require('@aws-cdk/aws-ec2'); +import cdk = require('@aws-cdk/cdk'); +import elbv2 = require('../lib'); + +const app = new cdk.App(process.argv); +const stack = new cdk.Stack(app, 'aws-cdk-elbv2-integ'); + +const vpc = new ec2.VpcNetwork(stack, 'VPC', { + maxAZs: 2 +}); + +const lb = new elbv2.NetworkLoadBalancer(stack, 'LB', { + vpc, + internetFacing: true +}); + +const listener = lb.addListener('Listener', { + port: 443, +}); + +const group = listener.addTargets('Target', { + port: 443, + targets: [new elbv2.IpTarget('10.0.1.1')] +}); + +group.addDependency(vpc); + +// The target's security group must allow being routed by the LB and the clients. + +process.stdout.write(app.run()); diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/test.listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/test.listener.ts new file mode 100644 index 0000000000000..23bdae7feafe6 --- /dev/null +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/test.listener.ts @@ -0,0 +1,115 @@ +import { expect, haveResource } from '@aws-cdk/assert'; +import ec2 = require('@aws-cdk/aws-ec2'); +import cdk = require('@aws-cdk/cdk'); +import { Test } from 'nodeunit'; +import elbv2 = require('../../lib'); +import { FakeSelfRegisteringTarget } from '../helpers'; + +export = { + 'Trivial add listener'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'Stack'); + const lb = new elbv2.NetworkLoadBalancer(stack, 'LB', { vpc }); + + // WHEN + lb.addListener('Listener', { + port: 443, + defaultTargetGroups: [new elbv2.NetworkTargetGroup(stack, 'Group', { vpc, port: 80 })] + }); + + // THEN + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::Listener', { + Protocol: 'TCP', + Port: 443 + })); + + test.done(); + }, + + 'Can add target groups'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'Stack'); + const lb = new elbv2.NetworkLoadBalancer(stack, 'LB', { vpc }); + const listener = lb.addListener('Listener', { port: 443 }); + const group = new elbv2.NetworkTargetGroup(stack, 'TargetGroup', { vpc, port: 80 }); + + // WHEN + listener.addTargetGroups('Default', group); + + // THEN + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::Listener', { + DefaultActions: [ + { + TargetGroupArn: { Ref: "TargetGroup3D7CD9B8" }, + Type: "forward" + } + ], + })); + + test.done(); + }, + + 'Can implicitly create target groups'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'Stack'); + const lb = new elbv2.NetworkLoadBalancer(stack, 'LB', { vpc }); + const listener = lb.addListener('Listener', { port: 443 }); + + // WHEN + listener.addTargets('Targets', { + port: 80, + targets: [new elbv2.InstanceTarget('i-12345')] + }); + + // THEN + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::Listener', { + DefaultActions: [ + { + TargetGroupArn: { Ref: "LBListenerTargetsGroup76EF81E8" }, + Type: "forward" + } + ], + })); + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::TargetGroup', { + VpcId: { Ref: "Stack8A423254" }, + Port: 80, + Protocol: "TCP", + Targets: [ + { Id: "i-12345" } + ] + })); + + test.done(); + }, + + 'Enable health check for targets'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'Stack'); + const lb = new elbv2.NetworkLoadBalancer(stack, 'LB', { vpc }); + const listener = lb.addListener('Listener', { port: 443 }); + + // WHEN + const group = listener.addTargets('Group', { + port: 80, + targets: [new FakeSelfRegisteringTarget(stack, 'Target', vpc)] + }); + group.configureHealthCheck({ + timeoutSeconds: 3600, + intervalSecs: 30, + path: '/test', + }); + + // THEN + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::TargetGroup', { + HealthCheckIntervalSeconds: 30, + HealthCheckPath: "/test", + HealthCheckTimeoutSeconds: 3600, + })); + + test.done(); + }, +}; diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/test.load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/test.load-balancer.ts new file mode 100644 index 0000000000000..6431adb03f7f4 --- /dev/null +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/test.load-balancer.ts @@ -0,0 +1,78 @@ +import { expect, haveResource } from '@aws-cdk/assert'; +import ec2 = require('@aws-cdk/aws-ec2'); +import cdk = require('@aws-cdk/cdk'); +import { Test } from 'nodeunit'; +import elbv2 = require('../../lib'); + +export = { + 'Trivial construction: internet facing'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'Stack'); + + // WHEN + new elbv2.NetworkLoadBalancer(stack, 'LB', { + vpc, + internetFacing: true, + }); + + // THEN + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::LoadBalancer', { + Scheme: "internet-facing", + Subnets: [ + { Ref: "StackPublicSubnet1Subnet0AD81D22" }, + { Ref: "StackPublicSubnet2Subnet3C7D2288" }, + { Ref: "StackPublicSubnet3SubnetCC1055D9" } + ], + Type: "network" + })); + + test.done(); + }, + + 'Trivial construction: internal'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'Stack'); + + // WHEN + new elbv2.NetworkLoadBalancer(stack, 'LB', { vpc }); + + // THEN + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::LoadBalancer', { + Scheme: "internal", + Subnets: [ + { Ref: "StackPrivateSubnet1Subnet47AC2BC7" }, + { Ref: "StackPrivateSubnet2SubnetA2F8EDD8" }, + { Ref: "StackPrivateSubnet3Subnet28548F2E" } + ], + Type: "network" + })); + + test.done(); + }, + + 'Attributes'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'Stack'); + + // WHEN + new elbv2.NetworkLoadBalancer(stack, 'LB', { + vpc, + crossZoneEnabled: true, + }); + + // THEN + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::LoadBalancer', { + LoadBalancerAttributes: [ + { + Key: "load_balancing.cross_zone.enabled", + Value: "true" + } + ] + })); + + test.done(); + }, +}; diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/test.elasticloadbalancingv2.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/test.elasticloadbalancingv2.ts deleted file mode 100644 index db4c843199541..0000000000000 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/test.elasticloadbalancingv2.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Test, testCase } from 'nodeunit'; - -exports = testCase({ - notTested(test: Test) { - test.ok(true, 'No tests are specified for this package.'); - test.done(); - } -}); diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/test.security-groups.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/test.security-groups.ts deleted file mode 100644 index 28f9c778eb333..0000000000000 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/test.security-groups.ts +++ /dev/null @@ -1,17 +0,0 @@ -import cdk = require('@aws-cdk/cdk'); -import { Test } from 'nodeunit'; - -export = { - 'bidirectional security groups are automatically added'(test: Test) { - // GIVEN - - - - - test.done(); - }, - - 'adding the same targets twice also works'(test: Test) { - test.done(); - }, -}; \ No newline at end of file From f45434f21207f6d85c6e6e357ed163fdbf47726a Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Thu, 20 Sep 2018 15:16:59 +0200 Subject: [PATCH 05/10] Update types to match master --- build.sh | 25 +++++++++++++++++-- .../alb/application-listener-certificate.ts | 2 +- .../lib/alb/application-listener-rule.ts | 4 +-- .../lib/alb/application-listener.ts | 13 +++++----- .../lib/alb/application-load-balancer.ts | 3 +-- .../lib/alb/application-target-group.ts | 4 +-- .../lib/nlb/network-listener.ts | 5 ++-- .../lib/nlb/network-load-balancer.ts | 3 +-- .../lib/shared/base-listener.ts | 8 +++--- .../lib/shared/base-load-balancer.ts | 17 ++++++------- .../lib/shared/base-target-group.ts | 14 +++++------ .../lib/shared/imported.ts | 7 +++--- .../test/alb/test.listener.ts | 19 +++++++------- .../test/alb/test.load-balancer.ts | 2 +- 14 files changed, 72 insertions(+), 54 deletions(-) diff --git a/build.sh b/build.sh index 28997e424f743..5141e93f1fbe8 100755 --- a/build.sh +++ b/build.sh @@ -1,6 +1,27 @@ #!/bin/bash set -euo pipefail +bail="--no-bail" +while [[ "${1:-}" != "" ]]; do + case $1 in + -h|--help) + echo "Usage: build.sh [--bail|-b] [--force|-f]" + exit 1 + ;; + -b|--bail) + bail="--bail" + ;; + -f|--force) + export CDK_BUILD="--force" + ;; + *) + echo "Unrecognized parameter: $1" + exit 1 + ;; + esac + shift +done + if [ ! -d node_modules ]; then /bin/bash ./install.sh fi @@ -24,10 +45,10 @@ trap "rm -rf $MERKLE_BUILD_CACHE" EXIT echo "=============================================================================================" echo "building..." -time lerna run --no-bail --stream build || fail +time lerna run $bail --stream build || fail echo "=============================================================================================" echo "testing..." -lerna run --no-bail --stream test || fail +lerna run $bail --stream test || fail touch $BUILD_INDICATOR diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-certificate.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-certificate.ts index 327f8f2a420ac..408462dc30d51 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-certificate.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-certificate.ts @@ -16,7 +16,7 @@ export interface ApplicationListenerCertificateProps { * * Duplicates are not allowed. */ - certificateArns: cdk.Arn[]; + certificateArns: string[]; } /** diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts index 6b9dab6ce7679..547c2696c43ba 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts @@ -1,5 +1,5 @@ import cdk = require('@aws-cdk/cdk'); -import { cloudformation, ListenerRuleArn } from '../elasticloadbalancingv2.generated'; +import { cloudformation } from '../elasticloadbalancingv2.generated'; import { IApplicationListener } from './application-listener'; import { IApplicationTargetGroup } from './application-target-group'; @@ -61,7 +61,7 @@ export class ApplicationListenerRule extends cdk.Construct implements cdk.IDepen /** * The ARN of this rule */ - public readonly listenerRuleArn: ListenerRuleArn; + public readonly listenerRuleArn: string; /** * The elements of this rule to add ordering dependencies on diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts index 2cd2522c4cf37..dfdb16f5fd121 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts @@ -1,6 +1,5 @@ import ec2 = require('@aws-cdk/aws-ec2'); import cdk = require('@aws-cdk/cdk'); -import { ListenerArn } from '../elasticloadbalancingv2.generated'; import { BaseListener, ListenerRefProps } from '../shared/base-listener'; import { HealthCheck } from '../shared/base-target-group'; import { ApplicationProtocol, SslPolicy } from '../shared/enums'; @@ -32,7 +31,7 @@ export interface BaseApplicationListenerProps { /** * The certificates to use on this listener */ - certificateArns?: cdk.Arn[]; + certificateArns?: string[]; /** * The security policy that defines which ciphers and protocols are supported. @@ -78,7 +77,7 @@ export class ApplicationListener extends BaseListener implements IApplicationLis /** * ARNs of certificates added to this listener */ - private readonly certificateArns: cdk.Arn[]; + private readonly certificateArns: string[]; /** * Load balancer this listener is associated with @@ -119,7 +118,7 @@ export class ApplicationListener extends BaseListener implements IApplicationLis /** * Add one or more certificates to this listener. */ - public addCertificateArns(_id: string, ...arns: cdk.Arn[]): void { + public addCertificateArns(_id: string, arns: string[]): void { this.certificateArns.push(...arns); } @@ -228,12 +227,12 @@ export interface IApplicationListener extends ec2.IConnectable { /** * ARN of the listener */ - listenerArn: ListenerArn; + listenerArn: string; /** * Add one or more certificates to this listener. */ - addCertificateArns(id: string, ...arns: cdk.Arn[]): void; + addCertificateArns(id: string, arns: string[]): void; /** * Load balance incoming requests to the given target groups. @@ -271,7 +270,7 @@ class ImportedApplicationListener extends BaseImportedListener implements IAppli /** * Add one or more certificates to this listener. */ - public addCertificateArns(id: string, ...arns: cdk.Arn[]): void { + public addCertificateArns(id: string, arns: string[]): void { new ApplicationListenerCertificate(this, id, { listener: this, certificateArns: arns diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts index 759574cb58f3d..0235dd221f8bb 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts @@ -1,7 +1,6 @@ import ec2 = require('@aws-cdk/aws-ec2'); import s3 = require('@aws-cdk/aws-s3'); import cdk = require('@aws-cdk/cdk'); -import { LoadBalancerArn } from '../elasticloadbalancingv2.generated'; import { BaseLoadBalancer, BaseLoadBalancerProps, LoadBalancerRefProps } from '../shared/base-load-balancer'; import { IpAddressType } from '../shared/enums'; import { BaseImportedLoadBalancer } from '../shared/imported'; @@ -114,7 +113,7 @@ export interface IApplicationLoadBalancer extends ec2.IConnectable { /** * The ARN of this load balancer */ - readonly loadBalancerArn: LoadBalancerArn; + readonly loadBalancerArn: string; /** * The VPC this load balancer has been created in (if available) diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts index 65e67f832d5fb..ad39a2322ed56 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts @@ -117,8 +117,8 @@ export class ApplicationTargetGroup extends BaseTargetGroup { */ public registerConnectable(connectable: ec2.IConnectable, portRange?: ec2.IPortRange) { if (portRange === undefined) { - if (cdk.isToken(this.defaultPort)) { - portRange = new ec2.TcpPortFromAttribute(new cdk.Token(this.defaultPort)); + if (cdk.unresolved(this.defaultPort)) { + portRange = new ec2.TcpPortFromAttribute(this.defaultPort); } else { portRange = new ec2.TcpPort(parseInt(this.defaultPort, 10)); } diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener.ts index c3b0ff884ced8..1ecbd0ebe1e97 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener.ts @@ -1,5 +1,4 @@ import cdk = require('@aws-cdk/cdk'); -import { ListenerArn } from '../elasticloadbalancingv2.generated'; import { BaseListener, ListenerRefProps } from '../shared/base-listener'; import { HealthCheck } from '../shared/base-target-group'; import { Protocol } from '../shared/enums'; @@ -58,6 +57,8 @@ export class NetworkListener extends BaseListener implements INetworkListener { }); this.loadBalancer = props.loadBalancer; + + (props.defaultTargetGroups || []).forEach(this._addDefaultTargetGroup.bind(this)); } /** @@ -107,7 +108,7 @@ export interface INetworkListener { /** * ARN of the listener */ - listenerArn: ListenerArn; + listenerArn: string; } /** diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-load-balancer.ts index 9d5c440d96b3a..54790bccf35bb 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-load-balancer.ts @@ -1,6 +1,5 @@ import ec2 = require('@aws-cdk/aws-ec2'); import cdk = require('@aws-cdk/cdk'); -import { LoadBalancerArn } from '../elasticloadbalancingv2.generated'; import { BaseLoadBalancer, BaseLoadBalancerProps, LoadBalancerRefProps } from '../shared/base-load-balancer'; import { BaseImportedLoadBalancer } from '../shared/imported'; import { BaseNetworkListenerProps, NetworkListener } from './network-listener'; @@ -53,7 +52,7 @@ export interface INetworkLoadBalancer { /** * The ARN of this load balancer */ - readonly loadBalancerArn: LoadBalancerArn; + readonly loadBalancerArn: string; /** * The VPC this load balancer has been created in (if available) diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-listener.ts index 979c28548143a..4b7da4d11c838 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-listener.ts @@ -1,12 +1,12 @@ import cdk = require('@aws-cdk/cdk'); -import { cloudformation, ListenerArn } from '../elasticloadbalancingv2.generated'; +import { cloudformation } from '../elasticloadbalancingv2.generated'; import { ITargetGroup } from './base-target-group'; /** * Base class for listeners */ export abstract class BaseListener extends cdk.Construct { - public readonly listenerArn: ListenerArn; + public readonly listenerArn: string; private readonly defaultActions: any[] = []; constructor(parent: cdk.Construct, id: string, additionalProps: any) { @@ -25,7 +25,7 @@ export abstract class BaseListener extends cdk.Construct { */ public export(): ListenerRefProps { return { - listenerArn: new ListenerArn(new cdk.Output(this, 'ListenerArn', { value: this.listenerArn }).makeImportValue()) + listenerArn: new cdk.Output(this, 'ListenerArn', { value: this.listenerArn }).makeImportValue().toString() }; } @@ -57,5 +57,5 @@ export interface ListenerRefProps { /** * ARN of the listener */ - listenerArn: ListenerArn; + listenerArn: string; } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts index f279de8156155..276328e9abf1c 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts @@ -1,7 +1,6 @@ import ec2 = require('@aws-cdk/aws-ec2'); import cdk = require('@aws-cdk/cdk'); -import { cloudformation, LoadBalancerArn, LoadBalancerCanonicalHostedZoneId, LoadBalancerDnsName, - LoadBalancerFullName, LoadBalancerName } from '../elasticloadbalancingv2.generated'; +import { cloudformation } from '../elasticloadbalancingv2.generated'; import { Attributes, ifUndefined, renderAttributes } from './util'; /** @@ -51,35 +50,35 @@ export abstract class BaseLoadBalancer extends cdk.Construct { * * @example Z2P70J7EXAMPLE */ - public readonly canonicalHostedZoneId: LoadBalancerCanonicalHostedZoneId; + public readonly canonicalHostedZoneId: string; /** * The DNS name of this load balancer * * @example my-load-balancer-424835706.us-west-2.elb.amazonaws.com */ - public readonly dnsName: LoadBalancerDnsName; + public readonly dnsName: string; /** * The full name of this load balancer * * @example app/my-load-balancer/50dc6c495c0c9188 */ - public readonly fullName: LoadBalancerFullName; + public readonly fullName: string; /** * The name of this load balancer * * @example my-load-balancer */ - public readonly loadBalancerName: LoadBalancerName; + public readonly loadBalancerName: string; /** * The ARN of this load balancer * * @example arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-internal-load-balancer/50dc6c495c0c9188 */ - public readonly loadBalancerArn: LoadBalancerArn; + public readonly loadBalancerArn: string; /** * The VPC this load balancer has been created in, if available @@ -141,7 +140,7 @@ export abstract class BaseLoadBalancer extends cdk.Construct { */ public export(): LoadBalancerRefProps { return { - loadBalancerArn: new LoadBalancerArn(new cdk.Output(this, 'LoadBalancerArn', { value: this.loadBalancerArn }).makeImportValue()) + loadBalancerArn: new cdk.Output(this, 'LoadBalancerArn', { value: this.loadBalancerArn }).makeImportValue().toString() }; } } @@ -153,5 +152,5 @@ export interface LoadBalancerRefProps { /** * ARN of the load balancer */ - loadBalancerArn: LoadBalancerArn; + loadBalancerArn: string; } diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts index 58a4bfe51424e..3cf944ef54f0b 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts @@ -1,6 +1,6 @@ import ec2 = require('@aws-cdk/aws-ec2'); import cdk = require('@aws-cdk/cdk'); -import { cloudformation, TargetGroupArn, TargetGroupFullName, TargetGroupName } from '../elasticloadbalancingv2.generated'; +import { cloudformation } from '../elasticloadbalancingv2.generated'; import { Protocol, TargetType } from './enums'; import { Attributes, renderAttributes } from './util'; @@ -123,17 +123,17 @@ export abstract class BaseTargetGroup extends cdk.Construct implements ITargetGr /** * The ARN of the target group */ - public readonly targetGroupArn: TargetGroupArn; + public readonly targetGroupArn: string; /** * The full name of the target group */ - public readonly targetGroupFullName: TargetGroupFullName; + public readonly targetGroupFullName: string; /** * The name of the target group */ - public readonly targetGroupName: TargetGroupName; + public readonly targetGroupName: string; /** * Health check for the members of this target group @@ -222,7 +222,7 @@ export abstract class BaseTargetGroup extends cdk.Construct implements ITargetGr */ public export(): TargetGroupRefProps { return { - targetGroupArn: new TargetGroupArn(new cdk.Output(this, 'TargetGroupArn', { value: this.targetGroupArn }).makeImportValue()), + targetGroupArn: new cdk.Output(this, 'TargetGroupArn', { value: this.targetGroupArn }).makeImportValue().toString(), defaultPort: new cdk.Output(this, 'Port', { value: this.defaultPort }).makeImportValue().toString(), }; } @@ -261,7 +261,7 @@ export interface TargetGroupRefProps { /** * ARN of the target group */ - targetGroupArn: TargetGroupArn; + targetGroupArn: string; /** * Port target group is listening on @@ -276,7 +276,7 @@ export interface ITargetGroup { /** * ARN of the target group */ - readonly targetGroupArn: TargetGroupArn; + readonly targetGroupArn: string; } /** diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/imported.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/imported.ts index 87f82e0a11b95..55fcefeeb3ca5 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/imported.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/imported.ts @@ -1,6 +1,5 @@ import ec2 = require('@aws-cdk/aws-ec2'); import cdk = require('@aws-cdk/cdk'); -import { ListenerArn, LoadBalancerArn, TargetGroupArn } from "../elasticloadbalancingv2.generated"; import { ListenerRefProps } from './base-listener'; import { LoadBalancerRefProps } from "./base-load-balancer"; import { TargetGroupRefProps } from './base-target-group'; @@ -12,7 +11,7 @@ export class BaseImportedLoadBalancer extends cdk.Construct { /** * ARN of the load balancer */ - public readonly loadBalancerArn: LoadBalancerArn; + public readonly loadBalancerArn: string; /** * VPC of the load balancer @@ -35,7 +34,7 @@ export class BaseImportedListener extends cdk.Construct { /** * ARN of the listener */ - public readonly listenerArn: ListenerArn; + public readonly listenerArn: string; constructor(parent: cdk.Construct, id: string, props: ListenerRefProps) { super(parent, id); @@ -51,7 +50,7 @@ export class BaseImportedTargetGroup extends cdk.Construct { /** * ARN of the target group */ - public readonly targetGroupArn: TargetGroupArn; + public readonly targetGroupArn: string; constructor(parent: cdk.Construct, id: string, props: TargetGroupRefProps) { super(parent, id); diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.listener.ts index becde4d316f09..3fdd5699c665b 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.listener.ts @@ -15,7 +15,7 @@ export = { // WHEN lb.addListener('Listener', { port: 443, - certificateArns: [new cdk.Arn('')], + certificateArns: ['bla'], defaultTargetGroups: [new elbv2.ApplicationTargetGroup(stack, 'Group', { vpc, port: 80 })] }); @@ -71,7 +71,7 @@ export = { const stack = new cdk.Stack(); const vpc = new ec2.VpcNetwork(stack, 'Stack'); const lb = new elbv2.ApplicationLoadBalancer(stack, 'LB', { vpc }); - const listener = lb.addListener('Listener', { port: 443 }); + const listener = lb.addListener('Listener', { port: 80 }); const group = new elbv2.ApplicationTargetGroup(stack, 'TargetGroup', { vpc, port: 80 }); // WHEN @@ -117,7 +117,7 @@ export = { const stack = new cdk.Stack(); const vpc = new ec2.VpcNetwork(stack, 'Stack'); const lb = new elbv2.ApplicationLoadBalancer(stack, 'LB', { vpc }); - const listener = lb.addListener('Listener', { port: 443 }); + const listener = lb.addListener('Listener', { port: 80 }); // WHEN listener.addTargets('Targets', { @@ -176,7 +176,8 @@ export = { const listener = lb.addListener('Listener', { port: 443 }); // WHEN - listener.addCertificateArns('Arns', new cdk.Arn('cert')); + listener.addCertificateArns('Arns', ['cert']); + listener.addTargets('Targets', { port: 8080, targets: [new elbv2.IpTarget('1.2.3.4')] }); // THEN expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::Listener', { @@ -199,7 +200,7 @@ export = { const listener2 = elbv2.ApplicationListener.import(stack2, 'Listener', listener1.export()); // WHEN - listener2.addCertificateArns('Arns', new cdk.Arn('cert')); + listener2.addCertificateArns('Arns', ['cert']); // THEN expect(stack2).to(haveResource('AWS::ElasticLoadBalancingV2::ListenerCertificate', { @@ -216,7 +217,7 @@ export = { const stack = new cdk.Stack(); const vpc = new ec2.VpcNetwork(stack, 'Stack'); const lb = new elbv2.ApplicationLoadBalancer(stack, 'LB', { vpc }); - const listener = lb.addListener('Listener', { port: 443 }); + const listener = lb.addListener('Listener', { port: 80 }); // WHEN const group = listener.addTargets('Group', { @@ -246,12 +247,12 @@ export = { test.done(); }, - 'Enable health check for tagets targets'(test: Test) { + 'Enable health check for targets'(test: Test) { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.VpcNetwork(stack, 'Stack'); const lb = new elbv2.ApplicationLoadBalancer(stack, 'LB', { vpc }); - const listener = lb.addListener('Listener', { port: 443 }); + const listener = lb.addListener('Listener', { port: 80 }); // WHEN const group = listener.addTargets('Group', { @@ -278,7 +279,7 @@ export = { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.VpcNetwork(stack, 'VPC'); - const listener = elbv2.ApplicationListener.import(stack, 'Listener', { listenerArn: new elbv2.ListenerArn('ieks') }); + const listener = elbv2.ApplicationListener.import(stack, 'Listener', { listenerArn: 'ieks' }); const group = new elbv2.ApplicationTargetGroup(stack, 'TargetGroup', { vpc, port: 80 }); // WHEN diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.load-balancer.ts index 15cc702a1c1a1..26d4a7e31d434 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.load-balancer.ts @@ -115,7 +115,7 @@ export = { Statement: [ { Action: "s3:PutObject", - Principal: { AWS: { "Fn::Join": [ "", [ "arn:", { Ref: "AWS::Partition" }, ":iam::", "127311923021", ":root" ] ] } }, + Principal: { AWS: { "Fn::Join": [ "", [ "arn:", { Ref: "AWS::Partition" }, ":iam::127311923021:root" ] ] } }, Resource: { "Fn::Join": [ "", [ { "Fn::GetAtt": [ "AccessLoggingBucketA6D88F29", "Arn" ] }, "/", "", "*" ] ] } } ] From c17e472905e44ccedc1d8a3741ded5489cd75cc6 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Fri, 21 Sep 2018 11:46:43 +0200 Subject: [PATCH 06/10] Fix security group import/export, remove ImportedConnections object Also fix a bug in security groups (formatted rule as ingress where it should have been egress). --- packages/@aws-cdk/aws-ec2/lib/connections.ts | 31 ----- .../aws-ec2/lib/security-group-rule.ts | 4 +- .../@aws-cdk/aws-ec2/lib/security-group.ts | 2 +- .../@aws-cdk/aws-ec2/test/test.connections.ts | 36 +++++- .../aws-elasticloadbalancingv2/TODO.txt | 3 - .../lib/alb/application-listener.ts | 65 +++++++++- .../lib/alb/application-load-balancer.ts | 58 ++++++++- .../lib/nlb/network-listener.ts | 37 +++++- .../lib/nlb/network-load-balancer.ts | 44 ++++++- .../lib/shared/base-listener.ts | 19 --- .../lib/shared/base-load-balancer.ts | 20 +-- .../lib/shared/imported.ts | 42 ------ .../test/alb/test.listener.ts | 5 +- .../test/alb/test.security-groups.ts | 121 +++++++++++++++++- scripts/runtest.js | 16 +++ 15 files changed, 358 insertions(+), 145 deletions(-) delete mode 100644 packages/@aws-cdk/aws-elasticloadbalancingv2/TODO.txt diff --git a/packages/@aws-cdk/aws-ec2/lib/connections.ts b/packages/@aws-cdk/aws-ec2/lib/connections.ts index 5efe377c30c66..8c4957a2ab5a5 100644 --- a/packages/@aws-cdk/aws-ec2/lib/connections.ts +++ b/packages/@aws-cdk/aws-ec2/lib/connections.ts @@ -185,35 +185,4 @@ export class Connections { } this.allowTo(other, this.defaultPortRange, description); } - -} - -/** - * An instance of Connections that you can use on importerted classes that need to implement IConnectable - */ -export class ImportedConnections extends Connections { - constructor() { - super({ securityGroupRule: { - canInlineRule: false, - uniqueId: '', - toEgressRuleJSON() { return {}; }, - toIngressRuleJSON() { return {}; }, - } - }); - } - - /** - * Allow connections to the peer on the given port - */ - public allowTo(_other: IConnectable, _portRange: IPortRange, _description?: string) { - // FIXME: Record in metadata that we dropped these permissions - } - - /** - * Allow connections from the peer on the given port - */ - public allowFrom(_other: IConnectable, _portRange: IPortRange, _description?: string) { - // FIXME: Record in metadata that we dropped these permissions - } - } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/lib/security-group-rule.ts b/packages/@aws-cdk/aws-ec2/lib/security-group-rule.ts index ef0755ba60f74..f0ed533389a19 100644 --- a/packages/@aws-cdk/aws-ec2/lib/security-group-rule.ts +++ b/packages/@aws-cdk/aws-ec2/lib/security-group-rule.ts @@ -34,7 +34,7 @@ export class CidrIPv4 implements ISecurityGroupRule, IConnectable { public readonly uniqueId: string; constructor(private readonly cidrIp: string) { - this.uniqueId = cidrIp; + this.uniqueId = cidrIp.replace('/', '_'); } /** @@ -69,7 +69,7 @@ export class CidrIPv6 implements ISecurityGroupRule, IConnectable { public readonly uniqueId: string; constructor(private readonly cidrIpv6: string) { - this.uniqueId = cidrIpv6; + this.uniqueId = cidrIpv6.replace('/', '_'); } /** diff --git a/packages/@aws-cdk/aws-ec2/lib/security-group.ts b/packages/@aws-cdk/aws-ec2/lib/security-group.ts index 411d43e08e35a..7b1abe8371298 100644 --- a/packages/@aws-cdk/aws-ec2/lib/security-group.ts +++ b/packages/@aws-cdk/aws-ec2/lib/security-group.ts @@ -182,7 +182,7 @@ export class SecurityGroup extends SecurityGroupRef { } this.addDirectEgressRule({ - ...peer.toIngressRuleJSON(), + ...peer.toEgressRuleJSON(), ...connection.toRuleJSON(), description }); diff --git a/packages/@aws-cdk/aws-ec2/test/test.connections.ts b/packages/@aws-cdk/aws-ec2/test/test.connections.ts index aedb7330c1b6b..32ed4f1b8506b 100644 --- a/packages/@aws-cdk/aws-ec2/test/test.connections.ts +++ b/packages/@aws-cdk/aws-ec2/test/test.connections.ts @@ -1,7 +1,8 @@ import { expect, haveResource } from '@aws-cdk/assert'; import { Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; -import { Connections, IConnectable, SecurityGroup, SecurityGroupRef, TcpAllPorts, TcpPort, VpcNetwork } from '../lib'; +import { AllConnections, AnyIPv4, AnyIPv6, Connections, IConnectable, PrefixList, SecurityGroup, SecurityGroupRef, + TcpAllPorts, TcpPort, TcpPortFromAttribute, TcpPortRange, VpcNetwork } from '../lib'; export = { 'peering between two security groups does not recursive infinitely'(test: Test) { @@ -54,6 +55,39 @@ export = { ToPort: 65535 })); + test.done(); + }, + + 'peer between all types of peers and port range types'(test: Test) { + // GIVEN + const stack = new Stack(undefined, 'TestStack', { env: { account: '12345678', region: 'dummy' }}); + const vpc = new VpcNetwork(stack, 'VPC'); + const sg = new SecurityGroup(stack, 'SG', { vpc }); + + const peers = [ + new SecurityGroup(stack, 'PeerGroup', { vpc }), + new AnyIPv4(), + new AnyIPv6(), + new PrefixList('pl-012345'), + ]; + + const ports = [ + new TcpPort(1234), + new TcpPortFromAttribute("port!"), + new TcpAllPorts(), + new TcpPortRange(80, 90), + new AllConnections() + ]; + + // WHEN + for (const peer of peers) { + for (const port of ports) { + sg.connections.allowTo(peer, port); + } + } + + // THEN -- no crash + test.done(); } }; diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/TODO.txt b/packages/@aws-cdk/aws-elasticloadbalancingv2/TODO.txt deleted file mode 100644 index ffb92ac9dd24f..0000000000000 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/TODO.txt +++ /dev/null @@ -1,3 +0,0 @@ -- Integ Tests -- README -- Docstrings everywhere diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts index dfdb16f5fd121..7b07f4ed14155 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts @@ -1,9 +1,8 @@ import ec2 = require('@aws-cdk/aws-ec2'); import cdk = require('@aws-cdk/cdk'); -import { BaseListener, ListenerRefProps } from '../shared/base-listener'; +import { BaseListener } from '../shared/base-listener'; import { HealthCheck } from '../shared/base-target-group'; import { ApplicationProtocol, SslPolicy } from '../shared/enums'; -import { BaseImportedListener } from '../shared/imported'; import { determineProtocolAndPort } from '../shared/util'; import { ApplicationListenerCertificate } from './application-listener-certificate'; import { ApplicationListenerRule } from './application-listener-rule'; @@ -65,7 +64,7 @@ export class ApplicationListener extends BaseListener implements IApplicationLis /** * Import an existing listener */ - public static import(parent: cdk.Construct, id: string, props: ListenerRefProps): IApplicationListener { + public static import(parent: cdk.Construct, id: string, props: ApplicationListenerRefProps): IApplicationListener { return new ImportedApplicationListener(parent, id, props); } @@ -89,6 +88,11 @@ export class ApplicationListener extends BaseListener implements IApplicationLis */ private readonly protocol: ApplicationProtocol; + /** + * The default port on which this listener is listening + */ + private readonly defaultPort: number; + constructor(parent: cdk.Construct, id: string, props: ApplicationListenerProps) { const [protocol, port] = determineProtocolAndPort(props.protocol, props.port); @@ -104,6 +108,7 @@ export class ApplicationListener extends BaseListener implements IApplicationLis this.protocol = protocol; this.certificateArns = []; this.certificateArns.push(...(props.certificateArns || [])); + this.defaultPort = port; // This listener edits the securitygroup of the load balancer, // but adds its own default port. @@ -211,6 +216,17 @@ export class ApplicationListener extends BaseListener implements IApplicationLis return errors; } + /** + * Export this listener + */ + public export(): ApplicationListenerRefProps { + return { + listenerArn: new cdk.Output(this, 'ListenerArn', { value: this.listenerArn }).makeImportValue().toString(), + securityGroupId: this.connections.securityGroup!.export().securityGroupId, + defaultPort: new cdk.Output(this, 'Port', { value: this.defaultPort }).makeImportValue().toString(), + }; + } + /** * Add a default TargetGroup */ @@ -263,9 +279,46 @@ export interface IApplicationListener extends ec2.IConnectable { registerConnectable(connectable: ec2.IConnectable, portRange: ec2.IPortRange): void; } -class ImportedApplicationListener extends BaseImportedListener implements IApplicationListener { - // FIXME: Proper security group import? - public readonly connections: ec2.Connections = new ec2.ImportedConnections(); +/** + * Properties to reference an existing listener + */ +export interface ApplicationListenerRefProps { + /** + * ARN of the listener + */ + listenerArn: string; + + /** + * Security group ID of the load balancer this listener is associated with + */ + securityGroupId: string; + + /** + * The default port on which this listener is listening + */ + defaultPort?: string; +} + +class ImportedApplicationListener extends cdk.Construct implements IApplicationListener { + public readonly connections: ec2.Connections; + + /** + * ARN of the listener + */ + public readonly listenerArn: string; + + constructor(parent: cdk.Construct, id: string, props: ApplicationListenerRefProps) { + super(parent, id); + + this.listenerArn = props.listenerArn; + + const defaultPortRange = props.defaultPort !== undefined ? new ec2.TcpPortFromAttribute(props.defaultPort) : undefined; + + this.connections = new ec2.Connections({ + securityGroup: ec2.SecurityGroupRef.import(this, 'SecurityGroup', { securityGroupId: props.securityGroupId }), + defaultPortRange, + }); + } /** * Add one or more certificates to this listener. diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts index 0235dd221f8bb..43e64c5b37d4b 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts @@ -1,9 +1,8 @@ import ec2 = require('@aws-cdk/aws-ec2'); import s3 = require('@aws-cdk/aws-s3'); import cdk = require('@aws-cdk/cdk'); -import { BaseLoadBalancer, BaseLoadBalancerProps, LoadBalancerRefProps } from '../shared/base-load-balancer'; +import { BaseLoadBalancer, BaseLoadBalancerProps } from '../shared/base-load-balancer'; import { IpAddressType } from '../shared/enums'; -import { BaseImportedLoadBalancer } from '../shared/imported'; import { ApplicationListener, BaseApplicationListenerProps } from './application-listener'; /** @@ -48,7 +47,7 @@ export class ApplicationLoadBalancer extends BaseLoadBalancer implements IApplic /** * Import an existing Application Load Balancer */ - public static import(parent: cdk.Construct, id: string, props: LoadBalancerRefProps): IApplicationLoadBalancer { + public static import(parent: cdk.Construct, id: string, props: ApplicationLoadBalancerRefProps): IApplicationLoadBalancer { return new ImportedApplicationLoadBalancer(parent, id, props); } @@ -104,6 +103,16 @@ export class ApplicationLoadBalancer extends BaseLoadBalancer implements IApplic ...props }); } + + /** + * Export this load balancer + */ + public export(): ApplicationLoadBalancerRefProps { + return { + loadBalancerArn: new cdk.Output(this, 'LoadBalancerArn', { value: this.loadBalancerArn }).makeImportValue().toString(), + securityGroupId: this.securityGroup.export().securityGroupId, + }; + } } /** @@ -126,6 +135,21 @@ export interface IApplicationLoadBalancer extends ec2.IConnectable { addListener(id: string, props: BaseApplicationListenerProps): ApplicationListener; } +/** + * Properties to reference an existing load balancer + */ +export interface ApplicationLoadBalancerRefProps { + /** + * ARN of the load balancer + */ + loadBalancerArn: string; + + /** + * ID of the load balancer's security group + */ + securityGroupId: string; +} + // https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-access-logs.html#access-logging-bucket-permissions const ELBV2_ACCOUNTS: {[region: string]: string } = { 'us-east-1': '127311923021', @@ -152,11 +176,33 @@ const ELBV2_ACCOUNTS: {[region: string]: string } = { /** * An ApplicationLoadBalancer that has been defined elsewhere */ -class ImportedApplicationLoadBalancer extends BaseImportedLoadBalancer implements IApplicationLoadBalancer, ec2.IConnectable { - // FIXME: Proper security group export? - public readonly connections: ec2.Connections = new ec2.ImportedConnections(); +class ImportedApplicationLoadBalancer extends cdk.Construct implements IApplicationLoadBalancer, ec2.IConnectable { + /** + * Manage connections for this load balancer + */ + public readonly connections: ec2.Connections; + + /** + * ARN of the load balancer + */ + public readonly loadBalancerArn: string; + + /** + * VPC of the load balancer + * + * Always undefined. + */ public readonly vpc?: ec2.VpcNetworkRef; + constructor(parent: cdk.Construct, id: string, props: ApplicationLoadBalancerRefProps) { + super(parent, id); + + this.loadBalancerArn = props.loadBalancerArn; + this.connections = new ec2.Connections({ + securityGroup: ec2.SecurityGroupRef.import(this, 'SecurityGroup', { securityGroupId: props.securityGroupId }) + }); + } + public addListener(id: string, props: BaseApplicationListenerProps): ApplicationListener { return new ApplicationListener(this, id, { loadBalancer: this, diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener.ts index 1ecbd0ebe1e97..e4fd1f1c07548 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener.ts @@ -1,8 +1,7 @@ import cdk = require('@aws-cdk/cdk'); -import { BaseListener, ListenerRefProps } from '../shared/base-listener'; +import { BaseListener } from '../shared/base-listener'; import { HealthCheck } from '../shared/base-target-group'; import { Protocol } from '../shared/enums'; -import { BaseImportedListener } from '../shared/imported'; import { INetworkLoadBalancer } from './network-load-balancer'; import { INetworkLoadBalancerTarget, INetworkTargetGroup, NetworkTargetGroup } from './network-target-group'; @@ -40,7 +39,7 @@ export class NetworkListener extends BaseListener implements INetworkListener { /** * Import an existing listener */ - public static import(parent: cdk.Construct, id: string, props: ListenerRefProps): INetworkListener { + public static import(parent: cdk.Construct, id: string, props: NetworkListenerRefProps): INetworkListener { return new ImportedNetworkListener(parent, id, props); } @@ -99,6 +98,16 @@ export class NetworkListener extends BaseListener implements INetworkListener { return group; } + + /** + * Export this listener + */ + public export(): NetworkListenerRefProps { + return { + listenerArn: new cdk.Output(this, 'ListenerArn', { value: this.listenerArn }).makeImportValue().toString() + }; + } + } /** @@ -111,10 +120,30 @@ export interface INetworkListener { listenerArn: string; } +/** + * Properties to reference an existing listener + */ +export interface NetworkListenerRefProps { + /** + * ARN of the listener + */ + listenerArn: string; +} + /** * An imported Network Listener */ -class ImportedNetworkListener extends BaseImportedListener implements INetworkListener { +class ImportedNetworkListener extends cdk.Construct implements INetworkListener { + /** + * ARN of the listener + */ + public readonly listenerArn: string; + + constructor(parent: cdk.Construct, id: string, props: NetworkListenerRefProps) { + super(parent, id); + + this.listenerArn = props.listenerArn; + } } /** diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-load-balancer.ts index 54790bccf35bb..5611d5920f1b5 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-load-balancer.ts @@ -1,7 +1,6 @@ import ec2 = require('@aws-cdk/aws-ec2'); import cdk = require('@aws-cdk/cdk'); -import { BaseLoadBalancer, BaseLoadBalancerProps, LoadBalancerRefProps } from '../shared/base-load-balancer'; -import { BaseImportedLoadBalancer } from '../shared/imported'; +import { BaseLoadBalancer, BaseLoadBalancerProps } from '../shared/base-load-balancer'; import { BaseNetworkListenerProps, NetworkListener } from './network-listener'; /** @@ -20,7 +19,7 @@ export interface NetworkLoadBalancerProps extends BaseLoadBalancerProps { * Define a new network load balancer */ export class NetworkLoadBalancer extends BaseLoadBalancer implements INetworkLoadBalancer { - public static import(parent: cdk.Construct, id: string, props: LoadBalancerRefProps): INetworkLoadBalancer { + public static import(parent: cdk.Construct, id: string, props: NetworkLoadBalancerRefProps): INetworkLoadBalancer { return new ImportedNetworkLoadBalancer(parent, id, props); } @@ -43,6 +42,15 @@ export class NetworkLoadBalancer extends BaseLoadBalancer implements INetworkLoa ...props }); } + + /** + * Export this load balancer + */ + public export(): NetworkLoadBalancerRefProps { + return { + loadBalancerArn: new cdk.Output(this, 'LoadBalancerArn', { value: this.loadBalancerArn }).makeImportValue().toString() + }; + } } /** @@ -67,10 +75,38 @@ export interface INetworkLoadBalancer { addListener(id: string, props: BaseNetworkListenerProps): NetworkListener; } +/** + * Properties to reference an existing load balancer + */ +export interface NetworkLoadBalancerRefProps { + /** + * ARN of the load balancer + */ + loadBalancerArn: string; +} + /** * An imported network load balancer */ -class ImportedNetworkLoadBalancer extends BaseImportedLoadBalancer implements INetworkLoadBalancer { +class ImportedNetworkLoadBalancer extends cdk.Construct implements INetworkLoadBalancer { + /** + * ARN of the load balancer + */ + public readonly loadBalancerArn: string; + + /** + * VPC of the load balancer + * + * Always undefined. + */ + public readonly vpc?: ec2.VpcNetworkRef; + + constructor(parent: cdk.Construct, id: string, props: NetworkLoadBalancerRefProps) { + super(parent, id); + + this.loadBalancerArn = props.loadBalancerArn; + } + /** * Add a listener to this load balancer * diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-listener.ts index 4b7da4d11c838..f2a486a05a607 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-listener.ts @@ -20,15 +20,6 @@ export abstract class BaseListener extends cdk.Construct { this.listenerArn = resource.ref; } - /** - * Export this listener - */ - public export(): ListenerRefProps { - return { - listenerArn: new cdk.Output(this, 'ListenerArn', { value: this.listenerArn }).makeImportValue().toString() - }; - } - /** * Validate this listener */ @@ -49,13 +40,3 @@ export abstract class BaseListener extends cdk.Construct { }); } } - -/** - * Properties to reference an existing listener - */ -export interface ListenerRefProps { - /** - * ARN of the listener - */ - listenerArn: string; -} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts index 276328e9abf1c..195a615204f0c 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts @@ -135,22 +135,4 @@ export abstract class BaseLoadBalancer extends cdk.Construct { this.setAttribute(key, undefined); } - /** - * Export this load balancer - */ - public export(): LoadBalancerRefProps { - return { - loadBalancerArn: new cdk.Output(this, 'LoadBalancerArn', { value: this.loadBalancerArn }).makeImportValue().toString() - }; - } -} - -/** - * Properties to reference an existing load balancer - */ -export interface LoadBalancerRefProps { - /** - * ARN of the load balancer - */ - loadBalancerArn: string; -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/imported.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/imported.ts index 55fcefeeb3ca5..570adfef61f6c 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/imported.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/imported.ts @@ -1,48 +1,6 @@ -import ec2 = require('@aws-cdk/aws-ec2'); import cdk = require('@aws-cdk/cdk'); -import { ListenerRefProps } from './base-listener'; -import { LoadBalancerRefProps } from "./base-load-balancer"; import { TargetGroupRefProps } from './base-target-group'; -/** - * Base class for existing load balancers - */ -export class BaseImportedLoadBalancer extends cdk.Construct { - /** - * ARN of the load balancer - */ - public readonly loadBalancerArn: string; - - /** - * VPC of the load balancer - * - * Always undefined. - */ - public readonly vpc?: ec2.VpcNetworkRef; - - constructor(parent: cdk.Construct, id: string, props: LoadBalancerRefProps) { - super(parent, id); - - this.loadBalancerArn = props.loadBalancerArn; - } -} - -/** - * Base class for existing listeners - */ -export class BaseImportedListener extends cdk.Construct { - /** - * ARN of the listener - */ - public readonly listenerArn: string; - - constructor(parent: cdk.Construct, id: string, props: ListenerRefProps) { - super(parent, id); - - this.listenerArn = props.listenerArn; - } -} - /** * Base class for existing target groups */ diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.listener.ts index 3fdd5699c665b..536755ef1c5d4 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.listener.ts @@ -279,7 +279,10 @@ export = { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.VpcNetwork(stack, 'VPC'); - const listener = elbv2.ApplicationListener.import(stack, 'Listener', { listenerArn: 'ieks' }); + const listener = elbv2.ApplicationListener.import(stack, 'Listener', { + listenerArn: 'ieks', + securityGroupId: 'sg-12345' + }); const group = new elbv2.ApplicationTargetGroup(stack, 'TargetGroup', { vpc, port: 80 }); // WHEN diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.security-groups.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.security-groups.ts index 90aebfdcb0161..a95485cebf19c 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.security-groups.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.security-groups.ts @@ -18,7 +18,7 @@ export = { }); // THEN - expectStandardBidi(fixture.stack); + expectSameStackSGRules(fixture.stack); test.done(); }, @@ -46,7 +46,7 @@ export = { }); // THEN - expectStandardBidi(fixture.stack); + expectSameStackSGRules(fixture.stack); test.done(); }, @@ -73,7 +73,7 @@ export = { }); // THEN - expectStandardBidi(fixture.stack); + expectSameStackSGRules(fixture.stack); test.done(); }, @@ -94,14 +94,123 @@ export = { group.addTarget(target); // THEN - expectStandardBidi(fixture.stack); + expectSameStackSGRules(fixture.stack); + + test.done(); + }, + + 'SG peering works on exported/imported load balancer'(test: Test) { + // GIVEN + const fixture = new TestFixture(); + const stack2 = new cdk.Stack(); + const vpc2 = new ec2.VpcNetwork(stack2, 'VPC'); + const group = new elbv2.ApplicationTargetGroup(stack2, 'TargetGroup', { + // We're assuming the 2nd VPC is peered to the 1st, or something. + vpc: vpc2, + port: 8008, + targets: [new FakeSelfRegisteringTarget(stack2, 'Target', vpc2)], + }); + + // WHEN + const lb2 = elbv2.ApplicationLoadBalancer.import(stack2, 'LB', fixture.lb.export()); + const listener2 = lb2.addListener('YetAnotherListener', { port: 80 }); + listener2.addTargetGroups('Default', { targetGroups: [group] }); + + // THEN + expectedImportedSGRules(stack2); + + test.done(); + }, + + 'SG peering works on exported/imported listener'(test: Test) { + // GIVEN + const fixture = new TestFixture(); + const stack2 = new cdk.Stack(); + const vpc2 = new ec2.VpcNetwork(stack2, 'VPC'); + const group = new elbv2.ApplicationTargetGroup(stack2, 'TargetGroup', { + // We're assuming the 2nd VPC is peered to the 1st, or something. + vpc: vpc2, + port: 8008, + targets: [new FakeSelfRegisteringTarget(stack2, 'Target', vpc2)], + }); + + // WHEN + const listener2 = elbv2.ApplicationListener.import(stack2, 'YetAnotherListener', fixture.listener.export()); + listener2.addTargetGroups('Default', { + // Must be a non-default target + priority: 10, + hostHeader: 'example.com', + targetGroups: [group] + }); + + // THEN + expectedImportedSGRules(stack2); + + test.done(); + }, + + 'default port peering works on constructed listener'(test: Test) { + // GIVEN + const fixture = new TestFixture(); + fixture.listener.addTargets('Default', { port: 8080, targets: [new elbv2.InstanceTarget('i-12345')] }); + + // WHEN + fixture.listener.connections.allowDefaultPortFromAnyIpv4('Open to the world'); + + // THEN + expect(fixture.stack).to(haveResource('AWS::EC2::SecurityGroup', { + SecurityGroupIngress: [ + { + CidrIp: "0.0.0.0/0", + Description: "Open to the world", + FromPort: 80, + IpProtocol: "tcp", + ToPort: 80 + } + ], + })); + + test.done(); + }, + + 'default port peering works on imported listener'(test: Test) { + // GIVEN + const fixture = new TestFixture(); + fixture.listener.addTargets('Default', { port: 8080, targets: [new elbv2.InstanceTarget('i-12345')] }); + const stack2 = new cdk.Stack(); + + // WHEN + const listener2 = elbv2.ApplicationListener.import(stack2, 'YetAnotherListener', fixture.listener.export()); + listener2.connections.allowDefaultPortFromAnyIpv4('Open to the world'); + + // THEN + expect(stack2).to(haveResource('AWS::EC2::SecurityGroupIngress', { + CidrIp: "0.0.0.0/0", + Description: "Open to the world", + IpProtocol: "tcp", + FromPort: { "Fn::ImportValue": "LBListenerPort7A9266A6" }, + ToPort: { "Fn::ImportValue": "LBListenerPort7A9266A6" }, + GroupId: IMPORTED_LB_SECURITY_GROUP + })); test.done(); }, }; -function expectStandardBidi(stack: cdk.Stack) { +const LB_SECURITY_GROUP = { "Fn::GetAtt": [ "LBSecurityGroup8A41EA2B", "GroupId" ] }; +const IMPORTED_LB_SECURITY_GROUP = { "Fn::ImportValue": "LBSecurityGroupSecurityGroupId0270B565" }; + +function expectSameStackSGRules(stack: cdk.Stack) { + expectSGRules(stack, LB_SECURITY_GROUP); +} + +function expectedImportedSGRules(stack: cdk.Stack) { + expectSGRules(stack, IMPORTED_LB_SECURITY_GROUP); +} + +function expectSGRules(stack: cdk.Stack, lbGroup: any) { expect(stack).to(haveResource('AWS::EC2::SecurityGroupEgress', { + GroupId: lbGroup, IpProtocol: "tcp", Description: "Load balancer to target", DestinationSecurityGroupId: { "Fn::GetAtt": [ "TargetSGDB98152D", "GroupId" ] }, @@ -113,7 +222,7 @@ function expectStandardBidi(stack: cdk.Stack) { Description: "Load balancer to target", FromPort: 8008, GroupId: { "Fn::GetAtt": [ "TargetSGDB98152D", "GroupId" ] }, - SourceSecurityGroupId: { "Fn::GetAtt": [ "LBSecurityGroup8A41EA2B", "GroupId" ] }, + SourceSecurityGroupId: lbGroup, ToPort: 8008 })); } diff --git a/scripts/runtest.js b/scripts/runtest.js index d501aca18f22c..3b4c56a253d80 100755 --- a/scripts/runtest.js +++ b/scripts/runtest.js @@ -1,5 +1,21 @@ #!/usr/bin/env node // Helper script to invoke 'nodeunit' on a .ts file. This should involve compilation, it doesn't right now. +// +// Example launch config to use with this script: +// +// { +// "configurations": [ +// { +// "type": "node", +// "request": "launch", +// "name": "Debug Current Unit Test File", +// "program": "${workspaceFolder}/scripts/runtest.js", +// "args": [ +// "${file}" +// ] +// } +// ] +// }⏎ const path = require('path'); // Unfortunately, nodeunit has no programmatic interface. Therefore, the From 1abad143e58fd81b2a56b42eab6259209f6a699f Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Fri, 21 Sep 2018 12:18:49 +0200 Subject: [PATCH 07/10] Review comments --- packages/@aws-cdk/aws-ec2/lib/connections.ts | 10 +++- .../aws-ec2/lib/security-group-rule.ts | 4 +- .../@aws-cdk/aws-ec2/lib/security-group.ts | 6 +- packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts | 6 +- .../aws-elasticloadbalancingv2/README.md | 55 ++++++++++++++----- .../lib/alb/application-listener.ts | 19 +++++++ .../test/alb/test.listener.ts | 2 +- .../test/alb/test.security-groups.ts | 2 +- 8 files changed, 81 insertions(+), 23 deletions(-) diff --git a/packages/@aws-cdk/aws-ec2/lib/connections.ts b/packages/@aws-cdk/aws-ec2/lib/connections.ts index 8c4957a2ab5a5..b49d8d5c7a134 100644 --- a/packages/@aws-cdk/aws-ec2/lib/connections.ts +++ b/packages/@aws-cdk/aws-ec2/lib/connections.ts @@ -69,9 +69,15 @@ export class Connections { */ public readonly securityGroup?: SecurityGroupRef; - private readonly securityGroupRule: ISecurityGroupRule; + /** + * The rule that defines how to represent this peer in a security group + */ + public readonly securityGroupRule: ISecurityGroupRule; - private readonly defaultPortRange?: IPortRange; + /** + * The default port configured for this connection peer, if available + */ + public readonly defaultPortRange?: IPortRange; constructor(props: ConnectionsProps) { if (!props.securityGroupRule && !props.securityGroup) { diff --git a/packages/@aws-cdk/aws-ec2/lib/security-group-rule.ts b/packages/@aws-cdk/aws-ec2/lib/security-group-rule.ts index f0ed533389a19..ef0755ba60f74 100644 --- a/packages/@aws-cdk/aws-ec2/lib/security-group-rule.ts +++ b/packages/@aws-cdk/aws-ec2/lib/security-group-rule.ts @@ -34,7 +34,7 @@ export class CidrIPv4 implements ISecurityGroupRule, IConnectable { public readonly uniqueId: string; constructor(private readonly cidrIp: string) { - this.uniqueId = cidrIp.replace('/', '_'); + this.uniqueId = cidrIp; } /** @@ -69,7 +69,7 @@ export class CidrIPv6 implements ISecurityGroupRule, IConnectable { public readonly uniqueId: string; constructor(private readonly cidrIpv6: string) { - this.uniqueId = cidrIpv6.replace('/', '_'); + this.uniqueId = cidrIpv6; } /** diff --git a/packages/@aws-cdk/aws-ec2/lib/security-group.ts b/packages/@aws-cdk/aws-ec2/lib/security-group.ts index 7b1abe8371298..b4df710d1eda5 100644 --- a/packages/@aws-cdk/aws-ec2/lib/security-group.ts +++ b/packages/@aws-cdk/aws-ec2/lib/security-group.ts @@ -32,10 +32,11 @@ export abstract class SecurityGroupRef extends Construct implements ISecurityGro public readonly defaultPortRange?: IPortRange; public addIngressRule(peer: ISecurityGroupRule, connection: IPortRange, description?: string) { - const id = `from ${peer.uniqueId}:${connection}`; + let id = `from ${peer.uniqueId}:${connection}`; if (description === undefined) { description = id; } + id = id.replace('/', '_'); // Skip duplicates if (this.tryFindChild(id) === undefined) { @@ -49,10 +50,11 @@ export abstract class SecurityGroupRef extends Construct implements ISecurityGro } public addEgressRule(peer: ISecurityGroupRule, connection: IPortRange, description?: string) { - const id = `to ${peer.uniqueId}:${connection}`; + let id = `to ${peer.uniqueId}:${connection}`; if (description === undefined) { description = id; } + id = id.replace('/', '_'); // Skip duplicates if (this.tryFindChild(id) === undefined) { diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts index 7b60c4c957d12..5e688c51251cf 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts @@ -160,7 +160,11 @@ export abstract class VpcNetworkRef extends Construct implements IDependable { } /** - * Return whether the given subnet is one of this VPC's public subnets + * Return whether the given subnet is one of this VPC's public subnets. + * + * The subnet must literally be one of the subnet object obtained from + * this VPC. A subnet that merely represents the same subnet will + * never return true. */ public isPublicSubnet(subnet: VpcSubnetRef) { return this.publicSubnets.indexOf(subnet) > -1; diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/README.md b/packages/@aws-cdk/aws-elasticloadbalancingv2/README.md index 24c2d1c3580ed..b7110d5ca5875 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/README.md +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/README.md @@ -3,6 +3,10 @@ The `@aws-cdk/aws-elasticloadbalancingv2` package provides constructs for configuring application and network load balancers. +For more information, see the AWS documentation for +[Application Load Balancers](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html) +and [Network Load Balancers](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/introduction.html). + ### Defining an Application Load Balancer You define an application load balancer by creating an instance of @@ -18,24 +22,26 @@ import autoscaling = require('@aws-cdk/aws-autoscaling'); const vpc = new ec2.VpcNetwork(...); -// Create the load balancer in a VPC. Set 'internetFacing' to 'false' to -// create an internal load balancer. +// Create the load balancer in a VPC. 'internetFacing' is 'false' +// by default, which creates an internal load balancer. const lb = new elbv2.ApplicationLoadBalancer(this, 'LB', { vpc, internetFacing: true }); // Add a listener and open up the load balancer's security group -// to the world. +// to the world. 'open' is the default, set this to 'false' +// and use `listener.connections` if you want to be selective +// about who can access the listener. const listener = lb.addListener('Listener', { port: 80, + open: true, }); -listener.connections.allowDefaultPortFromAnyIpv4('Open to the world'); // Create an AutoScaling group and add it as a load balancing // target to the listener. const asg = new autoscaling.AutoScalingGroup(...); -listener.addTargets('Targets', { +listener.addTargets('ApplicationFleet', { port: 8080, targets: [asg] }); @@ -48,11 +54,11 @@ updated to allow the network traffic. It's possible to route traffic to targets based on conditions in the incoming HTTP request. Path- and host-based conditions are supported. For example, -the following will route requests to the indicated AutoScalingGroup group +the following will route requests to the indicated AutoScalingGroup only if the requested host in the request is `example.com`: ```ts -listener.addTargets('Targets', { +listener.addTargets('Example.Com Fleet', { priority: 10, hostHeader: 'example.com', port: 8080, @@ -75,8 +81,8 @@ import ec2 = require('@aws-cdk/aws-ec2'); import elbv2 = require('@aws-cdk/aws-elasticloadbalancingv2'); import autoscaling = require('@aws-cdk/aws-autoscaling'); -// Create the load balancer in a VPC. Set 'internetFacing' to 'false' to -// create an internal load balancer. +// Create the load balancer in a VPC. 'internetFacing' is 'false' +// by default, which creates an internal load balancer. const lb = new elbv2.NetworkLoadBalancer(stack, 'LB', { vpc, internetFacing: true @@ -88,7 +94,7 @@ const listener = lb.addListener('Listener', { }); // Add targets on a particular port. -listener.addTargets('Target', { +listener.addTargets('AppFleet', { port: 443, targets: [asg] }); @@ -107,7 +113,7 @@ for more information. ### Targets and Target Groups Application and Network Load Balancers organize load balancing targets in Target -Groups. If you add your balancing targets (such as AutoScaling groups, ECS +Groups. If you add your balancing targets (such as AutoScalingGroups, ECS services or individual instances) to your listener directly, the appropriate `TargetGroup` will be automatically created for you. @@ -115,14 +121,23 @@ If you need more control over the Target Groups created, create an instance of `ApplicationTargetGroup` or `NetworkTargetGroup`, add the members you desire, and add it to the listener by calling `addTargetGroups` instead of `addTargets`. -`addTargets()` will always return the Target Group it just created for you. +`addTargets()` will always return the Target Group it just created for you: + +```ts +const group = listener.addTargets('AppFleet', { + port: 443, + targets: [asg1], +}); + +group.addTarget(asg2); +``` ### Configuring Health Checks Health checks are configured upon creation of a target group: ```ts -listener.addTargets('Targets', { +listener.addTargets('AppFleet', { port: 8080, targets: [asg], healthCheck: { @@ -138,7 +153,19 @@ The health check can also be configured after creation by calling No attempts are made to configure security groups for the port you're configuring a health check for, but if the health check is on the same port you're routing traffic to, the security group already allows the traffic. -If not, you will have to configure the security groups appropriately. +If not, you will have to configure the security groups appropriately: + +```ts +listener.addTargets('AppFleet', { + port: 8080, + targets: [asg], + healthCheck: { + port: 8088, + } +}); + +listener.connections.allowFrom(lb, new TcpPort(8088)); +``` ### Protocol for Load Balancer Targets diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts index 7b07f4ed14155..95db739ed61af 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts @@ -45,6 +45,21 @@ export interface BaseApplicationListenerProps { * @default None */ defaultTargetGroups?: IApplicationTargetGroup[]; + + /** + * Allow anyone to connect to this listener + * + * If this is specified, the listener will be opened up to anyone who can reach it. + * For internal load balancers this is anyone in the same VPC. For public load + * balancers, this is anyone on the internet. + * + * If you want to be more selective about who can access this load + * balancer, set this to `false` and use the listener's `connections` + * object to selectively grant access to the listener. + * + * @default true + */ + open?: boolean; } /** @@ -118,6 +133,10 @@ export class ApplicationListener extends BaseListener implements IApplicationLis }); (props.defaultTargetGroups || []).forEach(this.addDefaultTargetGroup.bind(this)); + + if (props.open) { + this.connections.allowDefaultPortFrom(new ec2.AnyIPv4(), `Allow from anyone on port ${port}`); + } } /** diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.listener.ts index 536755ef1c5d4..5e8578c70d110 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.listener.ts @@ -305,5 +305,5 @@ export = { })); test.done(); - } + }, }; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.security-groups.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.security-groups.ts index a95485cebf19c..f4525bfb9667d 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.security-groups.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.security-groups.ts @@ -239,6 +239,6 @@ class TestFixture { maxAZs: 2 }); this.lb = new elbv2.ApplicationLoadBalancer(this.stack, 'LB', { vpc: this.vpc }); - this.listener = this.lb.addListener('Listener', { port: 80 }); + this.listener = this.lb.addListener('Listener', { port: 80, open: false }); } } From 05084ac2d9cf7266e63249c49293ac7f0984b3e8 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Fri, 21 Sep 2018 12:50:12 +0200 Subject: [PATCH 08/10] Update expectations --- .../test/integ.asg-w-classic-loadbalancer.expected.json | 4 ++-- .../@aws-cdk/aws-autoscaling/test/test.auto-scaling-group.ts | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-classic-loadbalancer.expected.json b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-classic-loadbalancer.expected.json index 848931edf69db..c92cd2c79f071 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-classic-loadbalancer.expected.json +++ b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-classic-loadbalancer.expected.json @@ -458,7 +458,7 @@ } } }, - "FleetInstanceSecurityGroupPort80LBtofleetDC12B17A": { + "FleetInstanceSecurityGroupfromawscdkec2integLBSecurityGroupDEF4F99A8025E910CB": { "Type": "AWS::EC2::SecurityGroupIngress", "Properties": { "IpProtocol": "tcp", @@ -581,7 +581,7 @@ } } }, - "LBSecurityGroupPort80LBtofleet0986F2E8": { + "LBSecurityGrouptoawscdkec2integFleetInstanceSecurityGroupB03BE84D80B371C596": { "Type": "AWS::EC2::SecurityGroupEgress", "Properties": { "GroupId": { diff --git a/packages/@aws-cdk/aws-autoscaling/test/test.auto-scaling-group.ts b/packages/@aws-cdk/aws-autoscaling/test/test.auto-scaling-group.ts index 1f5964128aedb..124ef0e85386d 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/test.auto-scaling-group.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/test.auto-scaling-group.ts @@ -99,7 +99,6 @@ export = { "LaunchConfigurationName": { "Ref": "MyFleetLaunchConfig5D7F9801" }, - "LoadBalancerNames": [], "MaxSize": "1", "MinSize": "1", "VPCZoneIdentifier": [ @@ -229,7 +228,6 @@ export = { LaunchConfigurationName: { Ref: "MyFleetLaunchConfig5D7F9801" }, - LoadBalancerNames: [], MaxSize: "1", MinSize: "1", VPCZoneIdentifier: [ From 9bfc772b0346e85b6f6bfbe39ecc4f58d818ce1f Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Mon, 24 Sep 2018 10:06:04 +0200 Subject: [PATCH 09/10] Update test expectation --- packages/@aws-cdk/aws-rds/test/integ.cluster.expected.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-rds/test/integ.cluster.expected.json b/packages/@aws-cdk/aws-rds/test/integ.cluster.expected.json index 3a3c1b4ae947f..c4e75bcba0011 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.cluster.expected.json +++ b/packages/@aws-cdk/aws-rds/test/integ.cluster.expected.json @@ -340,7 +340,7 @@ } } }, - "DatabaseSecurityGroupOpentotheworld94E9606E": { + "DatabaseSecurityGroupfrom00000IndirectPortF24F2E03": { "Type": "AWS::EC2::SecurityGroupIngress", "Properties": { "IpProtocol": "tcp", From 084c949dfb085500531f3b4d3be0a39c940e492c Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Mon, 24 Sep 2018 13:42:51 +0200 Subject: [PATCH 10/10] Make these interface implementations readonly (Java compat) --- .../aws-elasticloadbalancingv2/lib/alb/application-listener.ts | 2 +- .../aws-elasticloadbalancingv2/lib/nlb/network-listener.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts index 95db739ed61af..5e6e692cc936f 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts @@ -262,7 +262,7 @@ export interface IApplicationListener extends ec2.IConnectable { /** * ARN of the listener */ - listenerArn: string; + readonly listenerArn: string; /** * Add one or more certificates to this listener. diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener.ts index e4fd1f1c07548..e02a20ad8a943 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener.ts @@ -117,7 +117,7 @@ export interface INetworkListener { /** * ARN of the listener */ - listenerArn: string; + readonly listenerArn: string; } /**