From 8571008884df8e048754fc4e0cfdf06ab20f0149 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Tue, 30 Mar 2021 15:04:54 +0200 Subject: [PATCH] feat(globalaccelerator): stabilize AWS Global Accelerator module (#13843) There are a number of changes to this module, made in order to stabilize it. The changes are as follows: * Endpoints as constructs would only work in TypeScript; they have been moved out as integration classes into `aws-globalaccelerator-endpoints` in order to support languages like Java and C#. * The automatic naming algorithm has been changed to reduce chances of conflict. * There are now convenience methods, `addListener()` and `addEndpointGroup()` that will create the appropriate objects, as alternatives to `new Listener()` and `new EndpointGroup()`. * EndpointGroups can take a list of `endpoints` in the constructor. * A Listener's `toPort` is optional (and defaults to `fromPort` if not supplied). * Support all the EndpointGroup properties. * An EndpointGroup's `region` is automatically determined from its configured endpoints, if possible. * The looked-up SecurityGroup is no longer accessible as a full Security Group, it can just be reference as a Peer (modifying the rules is not recommended by AGA and should not be allowed from the CDK). Changes to other libraries made to support this: * core, elbv2: imported Load Balancers now are aware of the region and account they were actually imported from, in order to be able to make `region` implicit in the AGA API. BREAKING CHANGE: automatic naming algorithm has been changed: if you have existing Accelerators you will need to pass an explicit name to prevent them from being replaced. All endpoints are now added by calling `addEndpoint()` with a target-specific class that can be found in `@aws-cdk/aws-globalaccelerator-endpoints`. The generated Security Group is now looked up by calling `endpointGroup.connectionsPeer()`. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-ec2/lib/peer.ts | 2 +- .../aws-ecs-patterns/test/ec2/test.l3s.ts | 4 +- .../test.load-balanced-fargate-service.ts | 4 +- .../lib/alb/application-load-balancer.ts | 9 +- .../lib/nlb/network-load-balancer.ts | 4 +- .../test/alb/load-balancer.test.ts | 19 +- .../test/nlb/load-balancer.test.ts | 18 +- .../.eslintrc.js | 3 + .../.gitignore | 22 ++ .../.npmignore | 27 ++ .../aws-globalaccelerator-endpoints/LICENSE | 201 +++++++++++++ .../aws-globalaccelerator-endpoints/NOTICE | 2 + .../aws-globalaccelerator-endpoints/README.md | 18 ++ .../jest.config.js | 11 + .../lib/_util.ts | 7 + .../lib/alb.ts | 50 ++++ .../lib/eip.ts | 38 +++ .../lib/index.ts | 4 + .../lib/instance.ts | 51 ++++ .../lib/nlb.ts | 36 +++ .../package.json | 105 +++++++ .../test/endpoints.test.ts | 183 ++++++++++++ .../integ.globalaccelerator.expected.json | 263 +++++++++++++++--- .../test/integ.globalaccelerator.ts | 29 +- .../@aws-cdk/aws-globalaccelerator/README.md | 217 +++++++++------ ...roup.ts => _accelerator-security-group.ts} | 37 ++- .../aws-globalaccelerator/lib/accelerator.ts | 15 +- .../lib/endpoint-group.ts | 254 ++++++++--------- .../aws-globalaccelerator/lib/endpoint.ts | 87 ++++++ .../aws-globalaccelerator/lib/index.ts | 2 +- .../aws-globalaccelerator/lib/listener.ts | 61 ++-- .../aws-globalaccelerator/package.json | 5 +- .../globalaccelerator-security-group.test.ts | 21 +- .../test/globalaccelerator.test.ts | 152 +++++----- .../aws-globalaccelerator/test/util.ts | 51 +--- packages/@aws-cdk/core/lib/resource.ts | 22 +- packages/@aws-cdk/core/test/resource.test.ts | 20 +- packages/aws-cdk-lib/package.json | 1 + packages/decdk/package.json | 1 + packages/monocdk/package.json | 1 + 40 files changed, 1602 insertions(+), 455 deletions(-) create mode 100644 packages/@aws-cdk/aws-globalaccelerator-endpoints/.eslintrc.js create mode 100644 packages/@aws-cdk/aws-globalaccelerator-endpoints/.gitignore create mode 100644 packages/@aws-cdk/aws-globalaccelerator-endpoints/.npmignore create mode 100644 packages/@aws-cdk/aws-globalaccelerator-endpoints/LICENSE create mode 100644 packages/@aws-cdk/aws-globalaccelerator-endpoints/NOTICE create mode 100644 packages/@aws-cdk/aws-globalaccelerator-endpoints/README.md create mode 100644 packages/@aws-cdk/aws-globalaccelerator-endpoints/jest.config.js create mode 100644 packages/@aws-cdk/aws-globalaccelerator-endpoints/lib/_util.ts create mode 100644 packages/@aws-cdk/aws-globalaccelerator-endpoints/lib/alb.ts create mode 100644 packages/@aws-cdk/aws-globalaccelerator-endpoints/lib/eip.ts create mode 100644 packages/@aws-cdk/aws-globalaccelerator-endpoints/lib/index.ts create mode 100644 packages/@aws-cdk/aws-globalaccelerator-endpoints/lib/instance.ts create mode 100644 packages/@aws-cdk/aws-globalaccelerator-endpoints/lib/nlb.ts create mode 100644 packages/@aws-cdk/aws-globalaccelerator-endpoints/package.json create mode 100644 packages/@aws-cdk/aws-globalaccelerator-endpoints/test/endpoints.test.ts rename packages/@aws-cdk/{aws-globalaccelerator => aws-globalaccelerator-endpoints}/test/integ.globalaccelerator.expected.json (79%) rename packages/@aws-cdk/{aws-globalaccelerator => aws-globalaccelerator-endpoints}/test/integ.globalaccelerator.ts (63%) rename packages/@aws-cdk/aws-globalaccelerator/lib/{accelerator-security-group.ts => _accelerator-security-group.ts} (72%) create mode 100644 packages/@aws-cdk/aws-globalaccelerator/lib/endpoint.ts diff --git a/packages/@aws-cdk/aws-ec2/lib/peer.ts b/packages/@aws-cdk/aws-ec2/lib/peer.ts index 7bd343b6f2828..333bd66bc91a9 100644 --- a/packages/@aws-cdk/aws-ec2/lib/peer.ts +++ b/packages/@aws-cdk/aws-ec2/lib/peer.ts @@ -198,4 +198,4 @@ class PrefixList implements IPeer { public toEgressRuleConfig(): any { return { destinationPrefixListId: this.prefixListId }; } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s.ts b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s.ts index d41a7684fd677..fa1d71cdb6d4a 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s.ts @@ -1198,7 +1198,7 @@ export = { 'NetworkLoadBalancedEC2Service accepts imported load balancer'(test: Test) { // GIVEN const stack = new cdk.Stack(); - const nlbArn = 'arn:aws:elasticloadbalancing::000000000000::dummyloadbalancer'; + const nlbArn = 'arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188'; const vpc = new ec2.Vpc(stack, 'Vpc'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc, clusterName: 'MyCluster' }); cluster.addCapacity('Capacity', { instanceType: new ec2.InstanceType('t2.micro') }); @@ -1274,7 +1274,7 @@ export = { 'ApplicationLoadBalancedEC2Service accepts imported load balancer'(test: Test) { // GIVEN const stack = new cdk.Stack(); - const albArn = 'arn:aws:elasticloadbalancing::000000000000::dummyloadbalancer'; + const albArn = 'arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188'; const vpc = new ec2.Vpc(stack, 'Vpc'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc, clusterName: 'MyCluster' }); cluster.addCapacity('Capacity', { instanceType: new ec2.InstanceType('t2.micro') }); diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.load-balanced-fargate-service.ts b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.load-balanced-fargate-service.ts index 3a17962c8b230..e7e847fb5cf45 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.load-balanced-fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.load-balanced-fargate-service.ts @@ -810,7 +810,7 @@ export = { const stack1 = new cdk.Stack(app, 'MyStack'); const vpc1 = new ec2.Vpc(stack1, 'VPC'); const cluster1 = new ecs.Cluster(stack1, 'Cluster', { vpc: vpc1 }); - const nlbArn = 'arn:aws:elasticloadbalancing::000000000000::dummyloadbalancer'; + const nlbArn = 'arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188'; const stack2 = new cdk.Stack(stack1, 'Stack2'); const cluster2 = ecs.Cluster.fromClusterAttributes(stack2, 'ImportedCluster', { vpc: vpc1, @@ -887,7 +887,7 @@ export = { 'passing in imported application load balancer and resources to ALB Fargate Service'(test: Test) { // GIVEN const stack1 = new cdk.Stack(); - const albArn = 'arn:aws:elasticloadbalancing::000000000000::dummyloadbalancer'; + const albArn = 'arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188'; const vpc = new ec2.Vpc(stack1, 'Vpc'); const cluster = new ecs.Cluster(stack1, 'Cluster', { vpc, clusterName: 'MyClusterName' }); const sg = new ec2.SecurityGroup(stack1, 'SecurityGroup', { vpc }); 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 4ad4dcb5fa081..d686396d5e9e0 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 @@ -562,7 +562,10 @@ class ImportedApplicationLoadBalancer extends Resource implements IApplicationLo public readonly vpc?: ec2.IVpc; constructor(scope: Construct, id: string, private readonly props: ApplicationLoadBalancerAttributes) { - super(scope, id); + super(scope, id, { + environmentFromArn: props.loadBalancerArn, + }); + this.vpc = props.vpc; this.loadBalancerArn = props.loadBalancerArn; this.connections = new ec2.Connections({ @@ -601,7 +604,9 @@ class LookedUpApplicationLoadBalancer extends Resource implements IApplicationLo public readonly vpc?: ec2.IVpc; constructor(scope: Construct, id: string, props: cxapi.LoadBalancerContextResponse) { - super(scope, id); + super(scope, id, { + environmentFromArn: props.loadBalancerArn, + }); this.loadBalancerArn = props.loadBalancerArn; this.loadBalancerCanonicalHostedZoneId = props.loadBalancerCanonicalHostedZoneId; 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 d6f7858114102..5261a3ce8a63d 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 @@ -102,7 +102,7 @@ export class NetworkLoadBalancer extends BaseLoadBalancer implements INetworkLoa } } - return new Import(scope, id); + return new Import(scope, id, { environmentFromArn: attrs.loadBalancerArn }); } constructor(scope: Construct, id: string, props: NetworkLoadBalancerProps) { @@ -306,7 +306,7 @@ class LookedUpNetworkLoadBalancer extends Resource implements INetworkLoadBalanc public readonly vpc?: ec2.IVpc; constructor(scope: Construct, id: string, props: cxapi.LoadBalancerContextResponse) { - super(scope, id); + super(scope, id, { environmentFromArn: props.loadBalancerArn }); this.loadBalancerArn = props.loadBalancerArn; this.loadBalancerCanonicalHostedZoneId = props.loadBalancerCanonicalHostedZoneId; diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/load-balancer.test.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/load-balancer.test.ts index f017a4a67f3fa..1ae34a272dac4 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/load-balancer.test.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/load-balancer.test.ts @@ -284,7 +284,7 @@ describe('tests', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Vpc'); - const albArn = 'myArn'; + const albArn = 'arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188'; const sg = new ec2.SecurityGroup(stack, 'sg', { vpc, securityGroupName: 'mySg', @@ -303,7 +303,7 @@ describe('tests', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Vpc'); - const albArn = 'MyArn'; + const albArn = 'arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188'; const sg = new ec2.SecurityGroup(stack, 'sg', { vpc, securityGroupName: 'mySg', @@ -319,6 +319,20 @@ describe('tests', () => { expect(() => listener.addTargets('Targets', { port: 8080 })).not.toThrow(); }); + test('imported load balancer knows its region', () => { + const stack = new cdk.Stack(); + + // WHEN + const albArn = 'arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188'; + const alb = elbv2.ApplicationLoadBalancer.fromApplicationLoadBalancerAttributes(stack, 'ALB', { + loadBalancerArn: albArn, + securityGroupId: 'sg-1234', + }); + + // THEN + expect(alb.env.region).toEqual('us-west-2'); + }); + test('can add secondary security groups', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Stack'); @@ -364,6 +378,7 @@ describe('tests', () => { expect(loadBalancer.loadBalancerDnsName).toEqual('my-load-balancer-1234567890.us-west-2.elb.amazonaws.com'); expect(loadBalancer.ipAddressType).toEqual(elbv2.IpAddressType.DUAL_STACK); expect(loadBalancer.connections.securityGroups[0].securityGroupId).toEqual('sg-12345'); + expect(loadBalancer.env.region).toEqual('us-west-2'); }); test('Can add listeners to a looked-up ApplicationLoadBalancer', () => { diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/load-balancer.test.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/load-balancer.test.ts index 546b88ab7d541..bb9e6a30ddecd 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/load-balancer.test.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/load-balancer.test.ts @@ -227,7 +227,7 @@ describe('tests', () => { test('imported network load balancer with no vpc specified throws error when calling addTargets', () => { // GIVEN const stack = new cdk.Stack(); - const nlbArn = 'arn:aws:elasticloadbalancing::000000000000::dummyloadbalancer'; + const nlbArn = 'arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188'; const nlb = elbv2.NetworkLoadBalancer.fromNetworkLoadBalancerAttributes(stack, 'NLB', { loadBalancerArn: nlbArn, }); @@ -240,7 +240,7 @@ describe('tests', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Vpc'); - const nlbArn = 'arn:aws:elasticloadbalancing::000000000000::dummyloadbalancer'; + const nlbArn = 'arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188'; const nlb = elbv2.NetworkLoadBalancer.fromNetworkLoadBalancerAttributes(stack, 'NLB', { loadBalancerArn: nlbArn, vpc, @@ -250,6 +250,19 @@ describe('tests', () => { expect(() => listener.addTargets('targetgroup', { port: 8080 })).not.toThrow(); }); + test('imported load balancer knows its region', () => { + const stack = new cdk.Stack(); + + // WHEN + const albArn = 'arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188'; + const alb = elbv2.NetworkLoadBalancer.fromNetworkLoadBalancerAttributes(stack, 'ALB', { + loadBalancerArn: albArn, + }); + + // THEN + expect(alb.env.region).toEqual('us-west-2'); + }); + test('Trivial construction: internal with Isolated subnets only', () => { // GIVEN const stack = new cdk.Stack(); @@ -429,6 +442,7 @@ describe('tests', () => { expect(loadBalancer.loadBalancerArn).toEqual('arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/network/my-load-balancer/50dc6c495c0c9188'); expect(loadBalancer.loadBalancerCanonicalHostedZoneId).toEqual('Z3DZXE0EXAMPLE'); expect(loadBalancer.loadBalancerDnsName).toEqual('my-load-balancer-1234567890.us-west-2.elb.amazonaws.com'); + expect(loadBalancer.env.region).toEqual('us-west-2'); }); test('Can add listeners to a looked-up NetworkLoadBalancer', () => { diff --git a/packages/@aws-cdk/aws-globalaccelerator-endpoints/.eslintrc.js b/packages/@aws-cdk/aws-globalaccelerator-endpoints/.eslintrc.js new file mode 100644 index 0000000000000..61dd8dd001f63 --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator-endpoints/.eslintrc.js @@ -0,0 +1,3 @@ +const baseConfig = require('cdk-build-tools/config/eslintrc'); +baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-globalaccelerator-endpoints/.gitignore b/packages/@aws-cdk/aws-globalaccelerator-endpoints/.gitignore new file mode 100644 index 0000000000000..2ed02868c78fb --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator-endpoints/.gitignore @@ -0,0 +1,22 @@ +*.js +tsconfig.json +*.js.map +*.d.ts +*.generated.ts +dist +lib/generated/resources.ts +.jsii + +.LAST_BUILD +.nyc_output +coverage +nyc.config.js +.LAST_PACKAGE +*.snk +.cdk.staging + +lib/sdk-api-metadata.json +!.eslintrc.js +!jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-globalaccelerator-endpoints/.npmignore b/packages/@aws-cdk/aws-globalaccelerator-endpoints/.npmignore new file mode 100644 index 0000000000000..63ab95621c764 --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator-endpoints/.npmignore @@ -0,0 +1,27 @@ +# Don't include original .ts files when doing `npm pack` +*.ts +!*.d.ts +coverage +.nyc_output +*.tgz + +dist +.LAST_PACKAGE +.LAST_BUILD +!*.js + +# Include .jsii +!.jsii + +*.snk + +*.tsbuildinfo + +tsconfig.json +.eslintrc.js +jest.config.js + +# exclude cdk artifacts +**/cdk.out +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-globalaccelerator-endpoints/LICENSE b/packages/@aws-cdk/aws-globalaccelerator-endpoints/LICENSE new file mode 100644 index 0000000000000..28e4bdcec77ec --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator-endpoints/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/@aws-cdk/aws-globalaccelerator-endpoints/NOTICE b/packages/@aws-cdk/aws-globalaccelerator-endpoints/NOTICE new file mode 100644 index 0000000000000..5fc3826926b5b --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator-endpoints/NOTICE @@ -0,0 +1,2 @@ +AWS Cloud Development Kit (AWS CDK) +Copyright 2018-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/packages/@aws-cdk/aws-globalaccelerator-endpoints/README.md b/packages/@aws-cdk/aws-globalaccelerator-endpoints/README.md new file mode 100644 index 0000000000000..d4fe7f8159b5d --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator-endpoints/README.md @@ -0,0 +1,18 @@ +# Endpoints for AWS Global Accelerator + + +--- + +![cdk-constructs: Stable](https://img.shields.io/badge/cdk--constructs-stable-success.svg?style=for-the-badge) + +--- + + + +This library contains integration classes to reference endpoints in AWS +Global Accelerator. Instances of these classes should be passed to the +`endpointGroup.addEndpoint()` method. + +See the README of the `@aws-cdk/aws-globalaccelerator` library for more information on +AWS Global Accelerator, and examples of all the integration classes available in +this module. diff --git a/packages/@aws-cdk/aws-globalaccelerator-endpoints/jest.config.js b/packages/@aws-cdk/aws-globalaccelerator-endpoints/jest.config.js new file mode 100644 index 0000000000000..49e81658a0875 --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator-endpoints/jest.config.js @@ -0,0 +1,11 @@ +const baseConfig = require('cdk-build-tools/config/jest.config'); +module.exports = { + ...baseConfig, + coverageThreshold: { + global: { + ...baseConfig.coverageThreshold.global, + branches: 50, + }, + }, +}; + diff --git a/packages/@aws-cdk/aws-globalaccelerator-endpoints/lib/_util.ts b/packages/@aws-cdk/aws-globalaccelerator-endpoints/lib/_util.ts new file mode 100644 index 0000000000000..aee44257d056b --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator-endpoints/lib/_util.ts @@ -0,0 +1,7 @@ +import { Token } from '@aws-cdk/core'; + +export function validateWeight(x?: number) { + if (x !== undefined && !Token.isUnresolved(x) && (x < 0 || x > 255)) { + throw new Error(`'weight' must be between 0 and 255, got: ${x}`); + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-globalaccelerator-endpoints/lib/alb.ts b/packages/@aws-cdk/aws-globalaccelerator-endpoints/lib/alb.ts new file mode 100644 index 0000000000000..1c4f618c16235 --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator-endpoints/lib/alb.ts @@ -0,0 +1,50 @@ +import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; +import * as ga from '@aws-cdk/aws-globalaccelerator'; +import { validateWeight } from './_util'; + +/** + * Properties for a ApplicationLoadBalancerEndpoint + */ +export interface ApplicationLoadBalancerEndpointOptions { + /** + * Endpoint weight across all endpoints in the group + * + * Must be a value between 0 and 255. + * + * @default 128 + */ + readonly weight?: number; + + /** + * Forward the client IP address in an `X-Forwarded-For` header + * + * GlobalAccelerator will create Network Interfaces in your VPC in order + * to preserve the client IP address. + * + * Client IP address preservation is supported only in specific AWS Regions. + * See the GlobalAccelerator Developer Guide for a list. + * + * @default true if available + */ + readonly preserveClientIp?: boolean; +} + +/** + * Use an Application Load Balancer as a Global Accelerator Endpoint + */ +export class ApplicationLoadBalancerEndpoint implements ga.IEndpoint { + public readonly region?: string; + + constructor(private readonly loadBalancer: elbv2.IApplicationLoadBalancer, private readonly options: ApplicationLoadBalancerEndpointOptions = {}) { + validateWeight(options.weight); + this.region = loadBalancer.env.region; + } + + public renderEndpointConfiguration(): any { + return { + endpointId: this.loadBalancer.loadBalancerArn, + weight: this.options.weight, + clientIpPreservationEnabled: this.options.preserveClientIp, + } as ga.CfnEndpointGroup.EndpointConfigurationProperty; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-globalaccelerator-endpoints/lib/eip.ts b/packages/@aws-cdk/aws-globalaccelerator-endpoints/lib/eip.ts new file mode 100644 index 0000000000000..924eb391efc0b --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator-endpoints/lib/eip.ts @@ -0,0 +1,38 @@ +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as ga from '@aws-cdk/aws-globalaccelerator'; +import { Stack } from '@aws-cdk/core'; +import { validateWeight } from './_util'; + +/** + * Properties for a NetworkLoadBalancerEndpoint + */ +export interface CfnEipEndpointProps { + /** + * Endpoint weight across all endpoints in the group + * + * Must be a value between 0 and 255. + * + * @default 128 + */ + readonly weight?: number; +} + +/** + * Use an EC2 Instance as a Global Accelerator Endpoint + */ +export class CfnEipEndpoint implements ga.IEndpoint { + public readonly region?: string; + + constructor(private readonly eip: ec2.CfnEIP, private readonly options: CfnEipEndpointProps = {}) { + validateWeight(options.weight); + + this.region = Stack.of(eip).region; + } + + public renderEndpointConfiguration(): any { + return { + endpointId: this.eip.attrAllocationId, + weight: this.options.weight, + } as ga.CfnEndpointGroup.EndpointConfigurationProperty; + } +} diff --git a/packages/@aws-cdk/aws-globalaccelerator-endpoints/lib/index.ts b/packages/@aws-cdk/aws-globalaccelerator-endpoints/lib/index.ts new file mode 100644 index 0000000000000..4286ae4cbd68c --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator-endpoints/lib/index.ts @@ -0,0 +1,4 @@ +export * from './alb'; +export * from './nlb'; +export * from './instance'; +export * from './eip'; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-globalaccelerator-endpoints/lib/instance.ts b/packages/@aws-cdk/aws-globalaccelerator-endpoints/lib/instance.ts new file mode 100644 index 0000000000000..a5d16298ec514 --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator-endpoints/lib/instance.ts @@ -0,0 +1,51 @@ +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as ga from '@aws-cdk/aws-globalaccelerator'; +import { validateWeight } from './_util'; + +/** + * Properties for a NetworkLoadBalancerEndpoint + */ +export interface InstanceEndpointProps { + /** + * Endpoint weight across all endpoints in the group + * + * Must be a value between 0 and 255. + * + * @default 128 + */ + readonly weight?: number; + + /** + * Forward the client IP address + * + * GlobalAccelerator will create Network Interfaces in your VPC in order + * to preserve the client IP address. + * + * Client IP address preservation is supported only in specific AWS Regions. + * See the GlobalAccelerator Developer Guide for a list. + * + * @default true if available + */ + readonly preserveClientIp?: boolean; +} + +/** + * Use an EC2 Instance as a Global Accelerator Endpoint + */ +export class InstanceEndpoint implements ga.IEndpoint { + public readonly region?: string; + + constructor(private readonly instance: ec2.IInstance, private readonly options: InstanceEndpointProps = {}) { + validateWeight(options.weight); + + this.region = instance.env.region; + } + + public renderEndpointConfiguration(): any { + return { + endpointId: this.instance.instanceId, + weight: this.options.weight, + clientIpPreservationEnabled: this.options.preserveClientIp, + } as ga.CfnEndpointGroup.EndpointConfigurationProperty; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-globalaccelerator-endpoints/lib/nlb.ts b/packages/@aws-cdk/aws-globalaccelerator-endpoints/lib/nlb.ts new file mode 100644 index 0000000000000..f94bfff5f5357 --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator-endpoints/lib/nlb.ts @@ -0,0 +1,36 @@ +import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; +import * as ga from '@aws-cdk/aws-globalaccelerator'; +import { validateWeight } from './_util'; + +/** + * Properties for a NetworkLoadBalancerEndpoint + */ +export interface NetworkLoadBalancerEndpointProps { + /** + * Endpoint weight across all endpoints in the group + * + * Must be a value between 0 and 255. + * + * @default 128 + */ + readonly weight?: number; +} + +/** + * Use a Network Load Balancer as a Global Accelerator Endpoint + */ +export class NetworkLoadBalancerEndpoint implements ga.IEndpoint { + public readonly region?: string; + + constructor(private readonly loadBalancer: elbv2.INetworkLoadBalancer, private readonly options: NetworkLoadBalancerEndpointProps = {}) { + validateWeight(options.weight); + this.region = loadBalancer.env.region; + } + + public renderEndpointConfiguration(): any { + return { + endpointId: this.loadBalancer.loadBalancerArn, + weight: this.options.weight, + } as ga.CfnEndpointGroup.EndpointConfigurationProperty; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-globalaccelerator-endpoints/package.json b/packages/@aws-cdk/aws-globalaccelerator-endpoints/package.json new file mode 100644 index 0000000000000..fa8fc01ec835a --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator-endpoints/package.json @@ -0,0 +1,105 @@ +{ + "name": "@aws-cdk/aws-globalaccelerator-endpoints", + "version": "0.0.0", + "description": "Endpoints for AWS Global Accelerator", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "jsii": { + "outdir": "dist", + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.GlobalAccelerator.Endpoints", + "packageId": "Amazon.CDK.AWS.GlobalAccelerator.Endpoints", + "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" + }, + "java": { + "package": "software.amazon.awscdk.services.globalaccelerator.endpoints", + "maven": { + "groupId": "software.amazon.awscdk", + "artifactId": "globalaccelerator-endpoints" + } + }, + "python": { + "distName": "aws-cdk.aws-globalaccelerator-endpoints", + "module": "aws_cdk.aws_globalaccelerator_endpoints", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] + } + }, + "projectReferences": true + }, + "repository": { + "type": "git", + "url": "https://github.com/aws/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-globalaccelerator-endpoints" + }, + "scripts": { + "build": "cdk-build", + "watch": "cdk-watch", + "lint": "cdk-lint", + "test": "cdk-test", + "integ": "cdk-integ", + "pkglint": "pkglint -f", + "package": "cdk-package", + "awslint": "cdk-awslint", + "build+test+package": "yarn build+test && yarn package", + "build+test": "yarn build && yarn test", + "compat": "cdk-compat", + "rosetta:extract": "yarn --silent jsii-rosetta extract" + }, + "cdk-build": { + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } + }, + "keywords": [ + "aws", + "cdk", + "constructs", + "globalaccelerator" + ], + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "devDependencies": { + "@aws-cdk/assert": "0.0.0", + "aws-sdk": "^2.848.0", + "aws-sdk-mock": "^5.1.0", + "cdk-build-tools": "0.0.0", + "cdk-integ-tools": "0.0.0", + "jest": "^26.6.3", + "pkglint": "0.0.0" + }, + "dependencies": { + "@aws-cdk/aws-ec2": "0.0.0", + "@aws-cdk/aws-elasticloadbalancingv2": "0.0.0", + "@aws-cdk/aws-globalaccelerator": "0.0.0", + "@aws-cdk/core": "0.0.0", + "constructs": "^3.3.69" + }, + "homepage": "https://github.com/aws/aws-cdk", + "peerDependencies": { + "@aws-cdk/aws-ec2": "0.0.0", + "@aws-cdk/aws-elasticloadbalancingv2": "0.0.0", + "@aws-cdk/aws-globalaccelerator": "0.0.0", + "@aws-cdk/core": "0.0.0", + "constructs": "^3.3.69" + }, + "engines": { + "node": ">= 10.13.0 <13 || >=13.7.0" + }, + "stability": "stable", + "awscdkio": { + "announce": false + }, + "maturity": "stable", + "publishConfig": { + "tag": "latest" + } +} diff --git a/packages/@aws-cdk/aws-globalaccelerator-endpoints/test/endpoints.test.ts b/packages/@aws-cdk/aws-globalaccelerator-endpoints/test/endpoints.test.ts new file mode 100644 index 0000000000000..068d885c32586 --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator-endpoints/test/endpoints.test.ts @@ -0,0 +1,183 @@ +import '@aws-cdk/assert/jest'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; +import * as ga from '@aws-cdk/aws-globalaccelerator'; +import { Stack } from '@aws-cdk/core'; +import * as endpoints from '../lib'; + +let stack: Stack; +let vpc: ec2.Vpc; +let accelerator: ga.Accelerator; +let listener: ga.Listener; +beforeEach(() => { + stack = new Stack(); + vpc = new ec2.Vpc(stack, 'Vpc'); + + accelerator = new ga.Accelerator(stack, 'Accelerator'); + listener = accelerator.addListener('Listener', { + portRanges: [{ fromPort: 80 }], + }); +}); + +test('Application Load Balancer with all properties', () => { + // WHEN + const alb = new elbv2.ApplicationLoadBalancer(stack, 'ALB', { vpc, internetFacing: true }); + listener.addEndpointGroup('Group', { + endpoints: [ + new endpoints.ApplicationLoadBalancerEndpoint(alb, { + weight: 50, + preserveClientIp: true, + }), + ], + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::GlobalAccelerator::EndpointGroup', { + EndpointConfigurations: [ + { + EndpointId: { Ref: 'ALBAEE750D2' }, + Weight: 50, + ClientIPPreservationEnabled: true, + }, + ], + }); +}); + +// Doesn't work yet because 'fromApplicationLoadBalancerAttributes' doesn't set the imported resource env +test('Get region from imported ALB', () => { + // WHEN + const alb = elbv2.ApplicationLoadBalancer.fromApplicationLoadBalancerAttributes(stack, 'ALB', { + loadBalancerArn: 'arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188', + securityGroupId: 'sg-1234', + }); + listener.addEndpointGroup('Group', { + endpoints: [ + new endpoints.ApplicationLoadBalancerEndpoint(alb), + ], + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::GlobalAccelerator::EndpointGroup', { + EndpointGroupRegion: 'us-west-2', + EndpointConfigurations: [ + { + EndpointId: 'arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188', + }, + ], + }); +}); + +test('Network Load Balancer with all properties', () => { + // WHEN + const nlb = new elbv2.NetworkLoadBalancer(stack, 'NLB', { vpc, internetFacing: true }); + listener.addEndpointGroup('Group', { + endpoints: [ + new endpoints.NetworkLoadBalancerEndpoint(nlb, { + weight: 50, + }), + ], + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::GlobalAccelerator::EndpointGroup', { + EndpointConfigurations: [ + { + EndpointId: { Ref: 'NLB55158F82' }, + Weight: 50, + }, + ], + }); +}); + +// Doesn't work yet because 'fromNetworkLoadBalancerAttributes' doesn't set the imported resource env +test('Get region from imported NLB', () => { + // WHEN + const nlb = elbv2.NetworkLoadBalancer.fromNetworkLoadBalancerAttributes(stack, 'NLB', { + loadBalancerArn: 'arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188', + }); + listener.addEndpointGroup('Group', { + endpoints: [ + new endpoints.NetworkLoadBalancerEndpoint(nlb), + ], + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::GlobalAccelerator::EndpointGroup', { + EndpointGroupRegion: 'us-west-2', + EndpointConfigurations: [ + { + EndpointId: 'arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188', + }, + ], + }); +}); + +test('CFN EIP with all properties', () => { + // WHEN + const eip = new ec2.CfnEIP(stack, 'ElasticIpAddress'); + listener.addEndpointGroup('Group', { + endpoints: [ + new endpoints.CfnEipEndpoint(eip, { + weight: 50, + }), + ], + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::GlobalAccelerator::EndpointGroup', { + EndpointConfigurations: [ + { + EndpointId: { 'Fn::GetAtt': ['ElasticIpAddress', 'AllocationId'] }, + Weight: 50, + }, + ], + }); +}); + +test('EC2 Instance with all properties', () => { + // WHEN + const instance = new ec2.Instance(stack, 'Instance', { + vpc, + machineImage: new ec2.AmazonLinuxImage(), + instanceType: new ec2.InstanceType('t3.small'), + }); + listener.addEndpointGroup('Group', { + endpoints: [ + new endpoints.InstanceEndpoint(instance, { + weight: 50, + preserveClientIp: true, + }), + ], + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::GlobalAccelerator::EndpointGroup', { + EndpointConfigurations: [ + { + EndpointId: { Ref: 'InstanceC1063A87' }, + Weight: 50, + ClientIPPreservationEnabled: true, + }, + ], + }); +}); + +test('throws if weight is not in range', () => { + // WHEN + const instance = new ec2.Instance(stack, 'Instance', { + vpc, + machineImage: new ec2.AmazonLinuxImage(), + instanceType: new ec2.InstanceType('t3.small'), + }); + + expect(() => { + listener.addEndpointGroup('Group', { + endpoints: [ + new endpoints.InstanceEndpoint(instance, { + weight: 300, + preserveClientIp: true, + }), + ], + }); + }).toThrow(/'weight' must be between 0 and 255/); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-globalaccelerator/test/integ.globalaccelerator.expected.json b/packages/@aws-cdk/aws-globalaccelerator-endpoints/test/integ.globalaccelerator.expected.json similarity index 79% rename from packages/@aws-cdk/aws-globalaccelerator/test/integ.globalaccelerator.expected.json rename to packages/@aws-cdk/aws-globalaccelerator-endpoints/test/integ.globalaccelerator.expected.json index 6e141037d0f36..36f2af987a0e2 100644 --- a/packages/@aws-cdk/aws-globalaccelerator/test/integ.globalaccelerator.expected.json +++ b/packages/@aws-cdk/aws-globalaccelerator-endpoints/test/integ.globalaccelerator.expected.json @@ -453,7 +453,7 @@ "Accelerator8EB0B6B1": { "Type": "AWS::GlobalAccelerator::Accelerator", "Properties": { - "Name": "Accelerator", + "Name": "integglobalacceleratorAccelerator5D88FB42", "Enabled": true } }, @@ -476,50 +476,6 @@ "ClientAffinity": "NONE" } }, - "GroupC77FDACD": { - "Type": "AWS::GlobalAccelerator::EndpointGroup", - "Properties": { - "EndpointGroupRegion": { - "Ref": "AWS::Region" - }, - "ListenerArn": { - "Fn::GetAtt": [ - "Listener828B0E81", - "ListenerArn" - ] - }, - "EndpointConfigurations": [ - { - "EndpointId": { - "Ref": "ALBAEE750D2" - } - }, - { - "EndpointId": { - "Ref": "NLB55158F82" - } - }, - { - "EndpointId": { - "Fn::GetAtt": [ - "ElasticIpAddress", - "AllocationId" - ] - } - }, - { - "EndpointId": { - "Ref": "Instance008A4B15C" - } - }, - { - "EndpointId": { - "Ref": "Instance14BC3991D" - } - } - ] - } - }, "ALBAEE750D2": { "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", "Properties": { @@ -575,6 +531,27 @@ } } }, + "ALBSecurityGroupfromGlobalAcceleratorGroup4435D2AC398": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "tcp", + "Description": "from GlobalAcceleratorGroup:443", + "FromPort": 443, + "GroupId": { + "Fn::GetAtt": [ + "ALBSecurityGroup8B8624F8", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "GroupPeerCustomResourceB3A15D36", + "SecurityGroups.0.GroupId" + ] + }, + "ToPort": 443 + } + }, "NLB55158F82": { "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", "Properties": { @@ -808,12 +785,208 @@ "DependsOn": [ "Instance1InstanceRoleBC4D05C6" ] + }, + "GroupC77FDACD": { + "Type": "AWS::GlobalAccelerator::EndpointGroup", + "Properties": { + "EndpointGroupRegion": { + "Ref": "AWS::Region" + }, + "ListenerArn": { + "Fn::GetAtt": [ + "Listener828B0E81", + "ListenerArn" + ] + }, + "EndpointConfigurations": [ + { + "EndpointId": { + "Ref": "ALBAEE750D2" + } + }, + { + "EndpointId": { + "Ref": "NLB55158F82" + } + }, + { + "EndpointId": { + "Fn::GetAtt": [ + "ElasticIpAddress", + "AllocationId" + ] + } + }, + { + "EndpointId": { + "Ref": "Instance008A4B15C" + } + }, + { + "EndpointId": { + "Ref": "Instance14BC3991D" + } + } + ] + } + }, + "GroupPeerCustomResourceCustomResourcePolicy42EF8263": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "ec2:DescribeSecurityGroups", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "GroupPeerCustomResourceCustomResourcePolicy42EF8263", + "Roles": [ + { + "Ref": "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2" + } + ] + }, + "DependsOn": [ + "GroupC77FDACD" + ] + }, + "GroupPeerCustomResourceB3A15D36": { + "Type": "Custom::AWS", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "AWS679f53fac002430cb0da5b7982bd22872D164C4C", + "Arn" + ] + }, + "Create": { + "Fn::Join": [ + "", + [ + "{\"service\":\"EC2\",\"action\":\"describeSecurityGroups\",\"parameters\":{\"Filters\":[{\"Name\":\"group-name\",\"Values\":[\"GlobalAccelerator\"]},{\"Name\":\"vpc-id\",\"Values\":[\"", + { + "Ref": "VPCB9E5F0B4" + }, + "\"]}]},\"physicalResourceId\":{\"responsePath\":\"SecurityGroups.0.GroupId\"}}" + ] + ] + }, + "InstallLatestAwsSdk": true + }, + "DependsOn": [ + "GroupPeerCustomResourceCustomResourcePolicy42EF8263", + "GroupC77FDACD" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "AWS679f53fac002430cb0da5b7982bd22872D164C4C": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParameters0625b1566df06e0ffd948f0f65f97a3d22d48242e66196d3f72b480f5309b343S3Bucket65227904" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters0625b1566df06e0ffd948f0f65f97a3d22d48242e66196d3f72b480f5309b343S3VersionKey3AF0E7DF" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters0625b1566df06e0ffd948f0f65f97a3d22d48242e66196d3f72b480f5309b343S3VersionKey3AF0E7DF" + } + ] + } + ] + } + ] + ] + } + }, + "Role": { + "Fn::GetAtt": [ + "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2", + "Arn" + ] + }, + "Handler": "index.handler", + "Runtime": "nodejs12.x", + "Timeout": 120 + }, + "DependsOn": [ + "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2" + ] } }, "Parameters": { "SsmParameterValueawsserviceamiamazonlinuxlatestamznamihvmx8664gp2C96584B6F00A464EAD1953AFF4B05118Parameter": { "Type": "AWS::SSM::Parameter::Value", "Default": "/aws/service/ami-amazon-linux-latest/amzn-ami-hvm-x86_64-gp2" + }, + "AssetParameters0625b1566df06e0ffd948f0f65f97a3d22d48242e66196d3f72b480f5309b343S3Bucket65227904": { + "Type": "String", + "Description": "S3 bucket for asset \"0625b1566df06e0ffd948f0f65f97a3d22d48242e66196d3f72b480f5309b343\"" + }, + "AssetParameters0625b1566df06e0ffd948f0f65f97a3d22d48242e66196d3f72b480f5309b343S3VersionKey3AF0E7DF": { + "Type": "String", + "Description": "S3 key for asset version \"0625b1566df06e0ffd948f0f65f97a3d22d48242e66196d3f72b480f5309b343\"" + }, + "AssetParameters0625b1566df06e0ffd948f0f65f97a3d22d48242e66196d3f72b480f5309b343ArtifactHash0C561FF5": { + "Type": "String", + "Description": "Artifact hash for asset \"0625b1566df06e0ffd948f0f65f97a3d22d48242e66196d3f72b480f5309b343\"" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-globalaccelerator/test/integ.globalaccelerator.ts b/packages/@aws-cdk/aws-globalaccelerator-endpoints/test/integ.globalaccelerator.ts similarity index 63% rename from packages/@aws-cdk/aws-globalaccelerator/test/integ.globalaccelerator.ts rename to packages/@aws-cdk/aws-globalaccelerator-endpoints/test/integ.globalaccelerator.ts index bee56b820d7ec..d4b5c2ada5ca1 100644 --- a/packages/@aws-cdk/aws-globalaccelerator/test/integ.globalaccelerator.ts +++ b/packages/@aws-cdk/aws-globalaccelerator-endpoints/test/integ.globalaccelerator.ts @@ -1,12 +1,11 @@ - import * as ec2 from '@aws-cdk/aws-ec2'; import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; -import * as cdk from '@aws-cdk/core'; +import * as ga from '@aws-cdk/aws-globalaccelerator'; +import { App, Stack } from '@aws-cdk/core'; import * as constructs from 'constructs'; -import * as ga from '../lib'; -import * as testfixture from './util'; +import * as endpoints from '../lib'; -class GaStack extends testfixture.TestStack { +class GaStack extends Stack { constructor(scope: constructs.Construct, id: string) { super(scope, id); @@ -21,7 +20,6 @@ class GaStack extends testfixture.TestStack { }, ], }); - const endpointGroup = new ga.EndpointGroup(this, 'Group', { listener }); const alb = new elbv2.ApplicationLoadBalancer(this, 'ALB', { vpc, internetFacing: true }); const nlb = new elbv2.NetworkLoadBalancer(this, 'NLB', { vpc, internetFacing: true }); const eip = new ec2.CfnEIP(this, 'ElasticIpAddress'); @@ -35,15 +33,20 @@ class GaStack extends testfixture.TestStack { })); } - endpointGroup.addLoadBalancer('AlbEndpoint', alb); - endpointGroup.addLoadBalancer('NlbEndpoint', nlb); - endpointGroup.addElasticIpAddress('EipEndpoint', eip); - endpointGroup.addEc2Instance('InstanceEndpoint', instances[0]); - endpointGroup.addEndpoint('InstanceEndpoint2', instances[1].instanceId); + const group = new ga.EndpointGroup(this, 'Group', { + listener, + endpoints: [ + new endpoints.ApplicationLoadBalancerEndpoint(alb), + new endpoints.NetworkLoadBalancerEndpoint(nlb), + new endpoints.CfnEipEndpoint(eip), + new endpoints.InstanceEndpoint(instances[0]), + new endpoints.InstanceEndpoint(instances[1]), + ], + }); + alb.connections.allowFrom(group.connectionsPeer('Peer', vpc), ec2.Port.tcp(443)); } } -const app = new cdk.App(); - +const app = new App(); new GaStack(app, 'integ-globalaccelerator'); diff --git a/packages/@aws-cdk/aws-globalaccelerator/README.md b/packages/@aws-cdk/aws-globalaccelerator/README.md index d959a2e9238ad..bdbb6780fadd2 100644 --- a/packages/@aws-cdk/aws-globalaccelerator/README.md +++ b/packages/@aws-cdk/aws-globalaccelerator/README.md @@ -5,17 +5,7 @@ ![cfn-resources: Stable](https://img.shields.io/badge/cfn--resources-stable-success.svg?style=for-the-badge) -> All classes with the `Cfn` prefix in this module ([CFN Resources]) are always stable and safe to use. -> -> [CFN Resources]: https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib - -![cdk-constructs: Experimental](https://img.shields.io/badge/cdk--constructs-experimental-important.svg?style=for-the-badge) - -> The APIs of higher level constructs in this module are experimental and under active development. -> They are subject to non-backward compatible changes or removal in any future version. These are -> not subject to the [Semantic Versioning](https://semver.org/) model and breaking changes will be -> announced in the release notes. This means that while you may use them, you may need to update -> your source code when upgrading to a newer version of this package. +![cdk-constructs: Stable](https://img.shields.io/badge/cdk--constructs-stable-success.svg?style=for-the-badge) --- @@ -23,115 +13,178 @@ ## Introduction -AWS Global Accelerator (AGA) is a service that improves the availability and performance of your applications with local or global users. It provides static IP addresses that act as a fixed entry point to your application endpoints in a single or multiple AWS Regions, such as your Application Load Balancers, Network Load Balancers or Amazon EC2 instances. +AWS Global Accelerator (AGA) is a service that improves the availability and +performance of your applications with local or global users. + +It intercepts your user's network connection at an edge location close to +them, and routes it to one of potentially multiple, redundant backends across +the more reliable and less congested AWS global network. -This module supports features under [AWS Global Accelerator](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_GlobalAccelerator.html) that allows users set up resources using the `@aws-cdk/aws-globalaccelerator` module. +AGA can be used to route traffic to Application Load Balancers, Network Load +Balancers, EC2 Instances and Elastic IP Addresses. -## Accelerator +For more information, see the [AWS Global +Accelerator Developer Guide](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_GlobalAccelerator.html). -The `Accelerator` resource is a Global Accelerator resource type that contains information about how you create an accelerator. An accelerator includes one or more listeners that process inbound connections and direct traffic to one or more endpoint groups, each of which includes endpoints, such as Application Load Balancers, Network Load Balancers, and Amazon EC2 instances. +## Example -To create the `Accelerator`: +Here's an example that sets up a Global Accelerator for two Application Load +Balancers in two different AWS Regions: ```ts import globalaccelerator = require('@aws-cdk/aws-globalaccelerator'); +import ga_endpoints = require('@aws-cdk/aws-globalaccelerator-endpoints'); +import elbv2 = require('@aws-cdk/aws-elasticloadbalancingv2'); -new globalaccelerator.Accelerator(stack, 'Accelerator'); +// Create an Accelerator +const accelerator = new globalaccelerator.Accelerator(stack, 'Accelerator'); + +// Create a Listener +const listener = accelerator.addListener('Listener', { + portRanges: [ + { fromPort: 80 }, + { fromPort: 443 }, + ], +}); + +// Import the Load Balancers +const nlb1 = elbv2.NetworkLoadBalancer.fromNetworkLoadBalancerAttributes(stack, 'NLB1', { + loadBalancerArn: 'arn:aws:elasticloadbalancing:us-west-2:111111111111:loadbalancer/app/my-load-balancer1/e16bef66805b', +}); +const nlb2 = elbv2.NetworkLoadBalancer.fromNetworkLoadBalancerAttributes(stack, 'NLB2', { + loadBalancerArn: 'arn:aws:elasticloadbalancing:ap-south-1:111111111111:loadbalancer/app/my-load-balancer2/5513dc2ea8a1', +}); +// Add one EndpointGroup for each Region we are targeting +listener.addEndpointGroup('Group1', { + endpoints: [new ga_endpoints.NetworkLoadBalancerEndpoint(nlb1)], +}); +listener.addEndpointGroup('Group2', { + // Imported load balancers automatically calculate their Region from the ARN. + // If you are load balancing to other resources, you must also pass a `region` + // parameter here. + endpoints: [new ga_endpoints.NetworkLoadBalancerEndpoint(nlb2)], +}); ``` -## Listener +## Concepts + +The **Accelerator** construct defines a Global Accelerator resource. + +An Accelerator includes one or more **Listeners** that accepts inbound +connections on one or more ports. + +Each Listener has one or more **Endpoint Groups**, representing multiple +geographically distributed copies of your application. There is one Endpoint +Group per Region, and user traffic is routed to the closest Region by default. + +An Endpoint Group consists of one or more **Endpoints**, which is where the +user traffic coming in on the Listener is ultimately sent. The Endpoint port +used is the same as the traffic came in on at the Listener, unless overridden. + +## Types of Endpoints + +There are 4 types of Endpoints, and they can be found in the +`@aws-cdk/aws-globalaccelerator-endpoints` package: -The `Listener` resource is a Global Accelerator resource type that contains information about how you create a listener to process inbound connections from clients to an accelerator. Connections arrive to assigned static IP addresses on a port, port range, or list of port ranges that you specify. +* Application Load Balancers +* Network Load Balancers +* EC2 Instances +* Elastic IP Addresses -To create the `Listener` listening on TCP 80: +### Application Load Balancers ```ts -new globalaccelerator.Listener(stack, 'Listener', { - accelerator, - portRanges: [ - { - fromPort: 80, - toPort: 80, - }, +const alb = new elbv2.ApplicationLoadBalancer(...); + +listener.addEndpointGroup('Group', { + endpoints: [ + new ga_endpoints.ApplicationLoadBalancerEndpoint(alb, { + weight: 128, + preserveClientIp: true, + }), ], }); ``` - -## EndpointGroup - -The `EndpointGroup` resource is a Global Accelerator resource type that contains information about how you create an endpoint group for the specified listener. An endpoint group is a collection of endpoints in one AWS Region. - -To create the `EndpointGroup`: +### Network Load Balancers ```ts -new globalaccelerator.EndpointGroup(stack, 'Group', { listener }); +const nlb = new elbv2.NetworkLoadBalancer(...); +listener.addEndpointGroup('Group', { + endpoints: [ + new ga_endpoints.NetworkLoadBalancerEndpoint(nlb, { + weight: 128, + }), + ], +}); ``` -## Add Endpoint into EndpointGroup - -You may use the following methods to add endpoints into the `EndpointGroup`: +### EC2 Instances -- `addEndpoint` to add a generic `endpoint` into the `EndpointGroup`. -- `addLoadBalancer` to add an Application Load Balancer or Network Load Balancer. -- `addEc2Instance` to add an EC2 Instance. -- `addElasticIpAddress` to add an Elastic IP Address. +```ts +const instance = new ec2.instance(...); + +listener.addEndpointGroup('Group', { + endpoints: [ + new ga_endpoints.InstanceEndpoint(instance, { + weight: 128, + preserveClientIp: true, + }), + ], +}); +``` +### Elastic IP Addresses ```ts -const endpointGroup = new globalaccelerator.EndpointGroup(stack, 'Group', { listener }); -const alb = new elbv2.ApplicationLoadBalancer(stack, 'ALB', { vpc, internetFacing: true }); -const nlb = new elbv2.NetworkLoadBalancer(stack, 'NLB', { vpc, internetFacing: true }); -const eip = new ec2.CfnEIP(stack, 'ElasticIpAddress'); -const instances = new Array(); - -for ( let i = 0; i < 2; i++) { - instances.push(new ec2.Instance(stack, `Instance${i}`, { - vpc, - machineImage: new ec2.AmazonLinuxImage(), - instanceType: new ec2.InstanceType('t3.small'), - })); -} - -endpointGroup.addLoadBalancer('AlbEndpoint', alb); -endpointGroup.addLoadBalancer('NlbEndpoint', nlb); -endpointGroup.addElasticIpAddress('EipEndpoint', eip); -endpointGroup.addEc2Instance('InstanceEndpoint', instances[0]); -endpointGroup.addEndpoint('InstanceEndpoint2', instances[1].instanceId); +const eip = new ec2.CfnEIP(...); + +listener.addEndpointGroup('Group', { + endpoints: [ + new ga_endpoints.CfnEipEndpoint(eip, { + weight: 128, + }), + ], +}); ``` -## Accelerator Security Groups +## Client IP Address Preservation and Security Groups -When using certain AGA features (client IP address preservation), AGA creates elastic network interfaces (ENI) in your AWS account which are -associated with a Security Group, and which are reused for all AGAs associated with that VPC. Per the -[best practices](https://docs.aws.amazon.com/global-accelerator/latest/dg/best-practices-aga.html) page, AGA creates a specific security group -called `GlobalAccelerator` for each VPC it has an ENI in. You can use the security group created by AGA as a source group in other security -groups, such as those for EC2 instances or Elastic Load Balancers, in order to implement least-privilege security group rules. +When using the `preserveClientIp` feature, AGA creates +**Elastic Network Interfaces** (ENIs) in your AWS account, that are +associated with a Security Group AGA creates for you. You can use the +security group created by AGA as a source group in other security groups +(such as those for EC2 instances or Elastic Load Balancers), if you want to +restrict incoming traffic to the AGA security group rules. -CloudFormation doesn't support referencing the security group created by AGA. CDK has a library that enables you to reference the AGA security group -for a VPC using an AwsCustomResource. +AGA creates a specific security group called `GlobalAccelerator` for each VPC +it has an ENI in (this behavior can not be changed). CloudFormation doesn't +support referencing the security group created by AGA, but this construct +library comes with a custom resource that enables you to reference the AGA +security group. + +Call `endpointGroup.connectionsPeer()` to obtain a reference to the Security Group +which you can use in connection rules. You must pass a reference to the VPC in whose +context the security group will be looked up. Example: ```ts -const vpc = new Vpc(stack, 'VPC', {}); -const alb = new elbv2.ApplicationLoadBalancer(stack, 'ALB', { vpc, internetFacing: false }); -const accelerator = new ga.Accelerator(stack, 'Accelerator'); -const listener = new ga.Listener(stack, 'Listener', { - accelerator, - portRanges: [ - { - fromPort: 443, - toPort: 443, - }, +// ... + +// Non-open ALB +const alb = new elbv2.ApplicationLoadBalancer(stack, 'ALB', { /* ... */ }); + +const endpointGroup = listener.addEndpointGroup('Group', { + endpoints: [ + new ga_endpoints.ApplicationLoadBalancerEndpoint(alb, { + preserveClientIps: true, + })], ], }); -const endpointGroup = new ga.EndpointGroup(stack, 'Group', { listener }); -endpointGroup.addLoadBalancer('AlbEndpoint', alb); // Remember that there is only one AGA security group per VPC. -// This code will fail at CloudFormation deployment time if you do not have an AGA -const agaSg = ga.AcceleratorSecurityGroup.fromVpc(stack, 'GlobalAcceleratorSG', vpc); +const agaSg = endpointGroup.connectionsPeer('GlobalAcceleratorSG', vpc); // Allow connections from the AGA to the ALB alb.connections.allowFrom(agaSg, Port.tcp(443)); diff --git a/packages/@aws-cdk/aws-globalaccelerator/lib/accelerator-security-group.ts b/packages/@aws-cdk/aws-globalaccelerator/lib/_accelerator-security-group.ts similarity index 72% rename from packages/@aws-cdk/aws-globalaccelerator/lib/accelerator-security-group.ts rename to packages/@aws-cdk/aws-globalaccelerator/lib/_accelerator-security-group.ts index 9197613d69b61..d59f572ecaebc 100644 --- a/packages/@aws-cdk/aws-globalaccelerator/lib/accelerator-security-group.ts +++ b/packages/@aws-cdk/aws-globalaccelerator/lib/_accelerator-security-group.ts @@ -1,16 +1,14 @@ -import { ISecurityGroup, SecurityGroup, IVpc } from '@aws-cdk/aws-ec2'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import { CfnResource } from '@aws-cdk/core'; import { AwsCustomResource, AwsCustomResourcePolicy, PhysicalResourceId } from '@aws-cdk/custom-resources'; +import { Construct } from 'constructs'; import { EndpointGroup } from '../lib'; -// keep this import separate from other imports to reduce chance for merge conflicts with v2-main -// eslint-disable-next-line no-duplicate-imports, import/order -import { Construct } from '@aws-cdk/core'; - /** * The security group used by a Global Accelerator to send traffic to resources in a VPC. */ -export class AcceleratorSecurityGroup { +export class AcceleratorSecurityGroupPeer implements ec2.IPeer { /** * Lookup the Global Accelerator security group at CloudFormation deployment time. * @@ -21,7 +19,7 @@ export class AcceleratorSecurityGroup { * the AGA security group for a given VPC at CloudFormation deployment time, and lets you create rules for traffic from AGA * to other resources created by CDK. */ - public static fromVpc(scope: Construct, id: string, vpc: IVpc, endpointGroup: EndpointGroup): ISecurityGroup { + public static fromVpc(scope: Construct, id: string, vpc: ec2.IVpc, endpointGroup: EndpointGroup) { // The security group name is always 'GlobalAccelerator' const globalAcceleratorSGName = 'GlobalAccelerator'; @@ -59,16 +57,27 @@ export class AcceleratorSecurityGroup { }), }); - // Look up the security group ID - const sg = SecurityGroup.fromSecurityGroupId(scope, - id, - lookupAcceleratorSGCustomResource.getResponseField(ec2ResponseSGIdField)); // We add a dependency on the endpoint group, guaranteeing that CloudFormation won't // try and look up the SG before AGA creates it. The SG is created when a VPC resource // is associated with an AGA - lookupAcceleratorSGCustomResource.node.addDependency(endpointGroup); - return sg; + lookupAcceleratorSGCustomResource.node.addDependency(endpointGroup.node.defaultChild as CfnResource); + + // Look up the security group ID + return new AcceleratorSecurityGroupPeer(lookupAcceleratorSGCustomResource.getResponseField(ec2ResponseSGIdField)); + } + + public readonly canInlineRule = false; + public readonly connections: ec2.Connections = new ec2.Connections({ peer: this }); + public readonly uniqueId: string = 'GlobalAcceleratorGroup'; + + private constructor(private readonly securityGroupId: string) { } - private constructor() {} + public toIngressRuleConfig(): any { + return { sourceSecurityGroupId: this.securityGroupId }; + } + + public toEgressRuleConfig(): any { + return { destinationSecurityGroupId: this.securityGroupId }; + } } diff --git a/packages/@aws-cdk/aws-globalaccelerator/lib/accelerator.ts b/packages/@aws-cdk/aws-globalaccelerator/lib/accelerator.ts index 721f641d3c19b..939e91f0a2839 100644 --- a/packages/@aws-cdk/aws-globalaccelerator/lib/accelerator.ts +++ b/packages/@aws-cdk/aws-globalaccelerator/lib/accelerator.ts @@ -1,6 +1,7 @@ import * as cdk from '@aws-cdk/core'; import { Construct } from 'constructs'; import * as ga from './globalaccelerator.generated'; +import { Listener, ListenerOptions } from './listener'; /** * The interface of the Accelerator @@ -63,7 +64,7 @@ export class Accelerator extends cdk.Resource implements IAccelerator { /** * import from attributes */ - public static fromAcceleratorAttributes(scope: Construct, id: string, attrs: AcceleratorAttributes ): IAccelerator { + public static fromAcceleratorAttributes(scope: Construct, id: string, attrs: AcceleratorAttributes): IAccelerator { class Import extends cdk.Resource implements IAccelerator { public readonly acceleratorArn = attrs.acceleratorArn; public readonly dnsName = attrs.dnsName; @@ -86,10 +87,20 @@ export class Accelerator extends cdk.Resource implements IAccelerator { const resource = new ga.CfnAccelerator(this, 'Resource', { enabled: props.enabled ?? true, - name: props.acceleratorName ?? id, + name: props.acceleratorName ?? cdk.Names.uniqueId(this), }); this.acceleratorArn = resource.attrAcceleratorArn; this.dnsName = resource.attrDnsName; } + + /** + * Add a listener to the accelerator + */ + public addListener(id: string, options: ListenerOptions) { + return new Listener(this, id, { + accelerator: this, + ...options, + }); + } } diff --git a/packages/@aws-cdk/aws-globalaccelerator/lib/endpoint-group.ts b/packages/@aws-cdk/aws-globalaccelerator/lib/endpoint-group.ts index b5c96bcd547ba..635ac85ff5708 100644 --- a/packages/@aws-cdk/aws-globalaccelerator/lib/endpoint-group.ts +++ b/packages/@aws-cdk/aws-globalaccelerator/lib/endpoint-group.ts @@ -1,12 +1,11 @@ +import * as ec2 from '@aws-cdk/aws-ec2'; import * as cdk from '@aws-cdk/core'; import { Construct } from 'constructs'; +import { AcceleratorSecurityGroupPeer } from './_accelerator-security-group'; +import { IEndpoint } from './endpoint'; import * as ga from './globalaccelerator.generated'; import { IListener } from './listener'; -// keep this import separate from other imports to reduce chance for merge conflicts with v2-main -// eslint-disable-next-line no-duplicate-imports, import/order -import { Construct as CoreConstruct } from '@aws-cdk/core'; - /** * The interface of the EndpointGroup */ @@ -19,125 +18,135 @@ export interface IEndpointGroup extends cdk.IResource { } /** - * Options for `addLoadBalancer`, `addElasticIpAddress` and `addEc2Instance` to add endpoints into the endpoint group + * Basic options for creating a new EndpointGroup */ -export interface EndpointConfigurationOptions { +export interface EndpointGroupOptions { /** - * Indicates whether client IP address preservation is enabled for an Application Load Balancer endpoint + * Name of the endpoint group * - * @default true + * @default - logical ID of the resource */ - readonly clientIpReservation?: boolean; + readonly endpointGroupName?: string; /** - * The weight associated with the endpoint. When you add weights to endpoints, you configure AWS Global Accelerator - * to route traffic based on proportions that you specify. For example, you might specify endpoint weights of 4, 5, - * 5, and 6 (sum=20). The result is that 4/20 of your traffic, on average, is routed to the first endpoint, 5/20 is - * routed both to the second and third endpoints, and 6/20 is routed to the last endpoint. - * @see https://docs.aws.amazon.com/global-accelerator/latest/dg/about-endpoints-endpoint-weights.html - * @default - not specified + * The AWS Region where the endpoint group is located. + * + * @default - region of the first endpoint in this group, or the stack region if that region can't be determined */ - readonly weight?: number; -} + readonly region?: string; -/** - * Properties to create EndpointConfiguration - * - */ -export interface EndpointConfigurationProps extends EndpointConfigurationOptions { /** - * The endopoint group reesource + * The time between health checks for each endpoint + * + * Must be either 10 or 30 seconds. * - * [disable-awslint:ref-via-interface] + * @default Duration.seconds(30) */ - readonly endpointGroup: EndpointGroup; + readonly healthCheckInterval?: cdk.Duration; /** - * An ID for the endpoint. If the endpoint is a Network Load Balancer or Application Load Balancer, - * this is the Amazon Resource Name (ARN) of the resource. If the endpoint is an Elastic IP address, - * this is the Elastic IP address allocation ID. For EC2 instances, this is the EC2 instance ID. + * The ping path for health checks (if the protocol is HTTP(S)). + * + * @default '/' */ - readonly endpointId: string; -} + readonly healthCheckPath?: string; -/** - * LoadBalancer Interface - */ -export interface LoadBalancer { /** - * The ARN of this load balancer + * The port used to perform health checks + * + * @default - The listener's port */ - readonly loadBalancerArn: string; -} + readonly healthCheckPort?: number; -/** - * EC2 Instance interface - */ -export interface Ec2Instance { /** - * The id of the instance resource + * The protocol used to perform health checks + * + * @default HealthCheckProtocol.TCP */ - readonly instanceId: string; -} + readonly healthCheckProtocol?: HealthCheckProtocol; -/** - * EIP Interface - */ -export interface ElasticIpAddress { /** - * allocation ID of the EIP resoruce + * The number of consecutive health checks required to set the state of a + * healthy endpoint to unhealthy, or to set an unhealthy endpoint to healthy. + * + * @default 3 */ - readonly attrAllocationId: string -} + readonly healthCheckThreshold?: number; -/** - * Property of the EndpointGroup - */ -export interface EndpointGroupProps { /** - * Name of the endpoint group + * The percentage of traffic to send to this AWS Region. * - * @default - logical ID of the resource + * The percentage is applied to the traffic that would otherwise have been + * routed to the Region based on optimal routing. Additional traffic is + * distributed to other endpoint groups for this listener. + * + * @default 100 */ - readonly endpointGroupName?: string; + readonly trafficDialPercentage?: number; /** - * The Amazon Resource Name (ARN) of the listener. + * Override the destination ports used to route traffic to an endpoint. + * + * Unless overridden, the port used to hit the endpoint will be the same as the port + * that traffic arrives on at the listener. + * + * @default - No overrides */ - readonly listener: IListener; + readonly portOverrides?: PortOverride[] /** - * The AWS Region where the endpoint group is located. + * Initial list of endpoints for this group * - * @default - the region of the current stack + * @default - Group is initially empty */ - readonly region?: string; + readonly endpoints?: IEndpoint[]; } /** - * The class for endpoint configuration + * Override specific listener ports used to route traffic to endpoints that are part of an endpoint group. */ -export class EndpointConfiguration extends CoreConstruct { +export interface PortOverride { /** - * The property containing all the configuration to be rendered + * The listener port that you want to map to a specific endpoint port. + * + * This is the port that user traffic arrives to the Global Accelerator on. */ - public readonly props: EndpointConfigurationProps; - constructor(scope: Construct, id: string, props: EndpointConfigurationProps) { - super(scope, id); - this.props = props; - props.endpointGroup._linkEndpoint(this); - } + readonly listenerPort: number; /** - * render the endpoint configuration for the endpoint group + * The endpoint port that you want a listener port to be mapped to. + * + * This is the port on the endpoint, such as the Application Load Balancer or Amazon EC2 instance. */ - public renderEndpointConfiguration(): ga.CfnEndpointGroup.EndpointConfigurationProperty { - return { - clientIpPreservationEnabled: this.props.clientIpReservation, - endpointId: this.props.endpointId, - weight: this.props.weight, - }; - } + readonly endpointPort: number; +} + +/** + * The protocol for the connections from clients to the accelerator. + */ +export enum HealthCheckProtocol { + /** + * TCP + */ + TCP = 'TCP', + /** + * HTTP + */ + HTTP = 'HTTP', + /** + * HTTPS + */ + HTTPS = 'HTTPS', +} + +/** + * Property of the EndpointGroup + */ +export interface EndpointGroupProps extends EndpointGroupOptions { + /** + * The Amazon Resource Name (ARN) of the listener. + */ + readonly listener: IListener; } /** @@ -165,75 +174,72 @@ export class EndpointGroup extends cdk.Resource implements IEndpointGroup { /** * The array of the endpoints in this endpoint group */ - protected readonly endpoints = new Array(); + protected readonly endpoints = new Array(); constructor(scope: Construct, id: string, props: EndpointGroupProps) { super(scope, id); const resource = new ga.CfnEndpointGroup(this, 'Resource', { listenerArn: props.listener.listenerArn, - endpointGroupRegion: props.region ?? cdk.Stack.of(this).region, + endpointGroupRegion: props.region ?? cdk.Lazy.string({ produce: () => this.firstEndpointRegion() }), endpointConfigurations: cdk.Lazy.any({ produce: () => this.renderEndpoints() }, { omitEmptyArray: true }), + healthCheckIntervalSeconds: props.healthCheckInterval?.toSeconds({ integral: true }), + healthCheckPath: props.healthCheckPath, + healthCheckPort: props.healthCheckPort, + healthCheckProtocol: props.healthCheckProtocol, + thresholdCount: props.healthCheckThreshold, + trafficDialPercentage: props.trafficDialPercentage, + portOverrides: props.portOverrides?.map(o => ({ + endpointPort: o.endpointPort, + listenerPort: o.listenerPort, + })), }); this.endpointGroupArn = resource.attrEndpointGroupArn; this.endpointGroupName = props.endpointGroupName ?? resource.logicalId; - } - /** - * Add an endpoint - */ - public addEndpoint(id: string, endpointId: string, props: EndpointConfigurationOptions = - {}) { - return new EndpointConfiguration(this, id, { - endpointGroup: this, - endpointId, - ...props, - }); + for (const endpoint of props.endpoints ?? []) { + this.addEndpoint(endpoint); + } } /** - * Add an Elastic Load Balancer as an endpoint in this endpoint group + * Add an endpoint */ - public addLoadBalancer(id: string, lb: LoadBalancer, props: EndpointConfigurationOptions = {}) { - return new EndpointConfiguration(this, id, { - endpointId: lb.loadBalancerArn, - endpointGroup: this, - ...props, - }); + public addEndpoint(endpoint: IEndpoint) { + this.endpoints.push(endpoint); } /** - * Add an EIP as an endpoint in this endpoint group + * Return an object that represents the Accelerator's Security Group + * + * Uses a Custom Resource to look up the Security Group that Accelerator + * creates at deploy time. Requires your VPC ID to perform the lookup. + * + * The Security Group will only be created if you enable **Client IP + * Preservation** on any of the endpoints. + * + * You cannot manipulate the rules inside this security group, but you can + * use this security group as a Peer in Connections rules on other + * constructs. */ - public addElasticIpAddress(id: string, eip: ElasticIpAddress, props: EndpointConfigurationOptions = {}) { - return new EndpointConfiguration(this, id, { - endpointId: eip.attrAllocationId, - endpointGroup: this, - ...props, - }); + public connectionsPeer(id: string, vpc: ec2.IVpc): ec2.IPeer { + return AcceleratorSecurityGroupPeer.fromVpc(this, id, vpc, this); } - /** - * Add an EC2 Instance as an endpoint in this endpoint group - */ - public addEc2Instance(id: string, instance: Ec2Instance, props: EndpointConfigurationOptions = {}) { - return new EndpointConfiguration(this, id, { - endpointId: instance.instanceId, - endpointGroup: this, - ...props, - }); + private renderEndpoints() { + return this.endpoints.map(e => e.renderEndpointConfiguration()); } /** - * Links a endpoint to this endpoint group - * @internal + * Return the first (readable) region of the endpoints in this group */ - public _linkEndpoint(endpoint: EndpointConfiguration) { - this.endpoints.push(endpoint); - } - - private renderEndpoints() { - return this.endpoints.map(e => e.renderEndpointConfiguration()); + private firstEndpointRegion() { + for (const endpoint of this.endpoints) { + if (endpoint.region) { + return endpoint.region; + } + } + return cdk.Stack.of(this).region; } } diff --git a/packages/@aws-cdk/aws-globalaccelerator/lib/endpoint.ts b/packages/@aws-cdk/aws-globalaccelerator/lib/endpoint.ts new file mode 100644 index 0000000000000..a99f54421fab1 --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator/lib/endpoint.ts @@ -0,0 +1,87 @@ +import { CfnEndpointGroup } from './globalaccelerator.generated'; + +/** + * An endpoint for the endpoint group + * + * Implementations of `IEndpoint` can be found in the `aws-globalaccelerator-endpoints` package. + */ +export interface IEndpoint { + /** + * The region where the endpoint is located + * + * If the region cannot be determined, `undefined` is returned + */ + readonly region?: string; + + /** + * Render the endpoint to an endpoint configuration + */ + renderEndpointConfiguration(): any; +} + +/** + * Properties for RawEndpoint + */ +export interface RawEndpointProps { + /** + * Identifier of the endpoint + * + * Load balancer ARN, instance ID or EIP allocation ID. + */ + readonly endpointId: string; + + /** + * Endpoint weight across all endpoints in the group + * + * Must be a value between 0 and 255. + * + * @default 128 + */ + readonly weight?: number; + + /** + * Forward the client IP address + * + * GlobalAccelerator will create Network Interfaces in your VPC in order + * to preserve the client IP address. + * + * Only applies to Application Load Balancers and EC2 instances. + * + * Client IP address preservation is supported only in specific AWS Regions. + * See the GlobalAccelerator Developer Guide for a list. + * + * @default true if possible and available + */ + readonly preserveClientIp?: boolean; + + /** + * The region where this endpoint is located + * + * @default - Unknown what region this endpoint is located + */ + readonly region?: string; +} + +/** + * Untyped endpoint implementation + * + * Prefer using the classes in the `aws-globalaccelerator-endpoints` package instead, + * as they accept typed constructs. You can use this class if you want to use an + * endpoint type that does not have an appropriate class in that package yet. + */ +export class RawEndpoint implements IEndpoint { + public readonly region?: string; + + constructor(private readonly props: RawEndpointProps) { + this.region = props.region; + } + + public renderEndpointConfiguration(): any { + return { + endpointId: this.props.endpointId, + weight: this.props.weight, + clientIpPreservationEnabled: this.props.preserveClientIp, + } as CfnEndpointGroup.EndpointConfigurationProperty; + } +} + diff --git a/packages/@aws-cdk/aws-globalaccelerator/lib/index.ts b/packages/@aws-cdk/aws-globalaccelerator/lib/index.ts index ff4675e6af2e5..de29a6d6ed08b 100644 --- a/packages/@aws-cdk/aws-globalaccelerator/lib/index.ts +++ b/packages/@aws-cdk/aws-globalaccelerator/lib/index.ts @@ -1,6 +1,6 @@ // AWS::GlobalAccelerator CloudFormation Resources: export * from './globalaccelerator.generated'; export * from './accelerator'; -export * from './accelerator-security-group'; export * from './listener'; export * from './endpoint-group'; +export * from './endpoint'; diff --git a/packages/@aws-cdk/aws-globalaccelerator/lib/listener.ts b/packages/@aws-cdk/aws-globalaccelerator/lib/listener.ts index 2f29bde4b03c7..39c5c0e4699b1 100644 --- a/packages/@aws-cdk/aws-globalaccelerator/lib/listener.ts +++ b/packages/@aws-cdk/aws-globalaccelerator/lib/listener.ts @@ -1,6 +1,7 @@ import * as cdk from '@aws-cdk/core'; import { Construct } from 'constructs'; import { IAccelerator } from './accelerator'; +import { EndpointGroup, EndpointGroupOptions } from './endpoint-group'; import * as ga from './globalaccelerator.generated'; /** @@ -16,9 +17,9 @@ export interface IListener extends cdk.IResource { } /** - * construct properties for Listener + * Construct options for Listener */ -export interface ListenerProps { +export interface ListenerOptions { /** * Name of the listener * @@ -26,11 +27,6 @@ export interface ListenerProps { */ readonly listenerName?: string; - /** - * The accelerator for this listener - */ - readonly accelerator: IAccelerator; - /** * The list of port ranges for the connections from clients to the accelerator */ @@ -39,18 +35,35 @@ export interface ListenerProps { /** * The protocol for the connections from clients to the accelerator * - * @default TCP + * @default ConnectionProtocol.TCP */ readonly protocol?: ConnectionProtocol; /** * Client affinity to direct all requests from a user to the same endpoint * - * @default NONE + * If you have stateful applications, client affinity lets you direct all + * requests from a user to the same endpoint. + * + * By default, each connection from each client is routed to seperate + * endpoints. Set client affinity to SOURCE_IP to route all connections from + * a single client to the same endpoint. + * + * @default ClientAffinity.NONE */ readonly clientAffinity?: ClientAffinity; } +/** + * Construct properties for Listener + */ +export interface ListenerProps extends ListenerOptions { + /** + * The accelerator for this listener + */ + readonly accelerator: IAccelerator; +} + /** * The list of port ranges for the connections from clients to the accelerator. */ @@ -58,11 +71,14 @@ export interface PortRange { /** * The first port in the range of ports, inclusive. */ - readonly fromPort: number, + readonly fromPort: number; + /** * The last port in the range of ports, inclusive. + * + * @default - same as `fromPort` */ - readonly toPort: number, + readonly toPort?: number; } /** @@ -80,20 +96,20 @@ export enum ConnectionProtocol { } /** - * Client affinity lets you direct all requests from a user to the same endpoint, if you have stateful applications, - * regardless of the port and protocol of the client request. Client affinity gives you control over whether to always - * route each client to the same specific endpoint. If you want a given client to always be routed to the same - * endpoint, set client affinity to SOURCE_IP. + * Client affinity gives you control over whether to always route each client to the same specific endpoint. * * @see https://docs.aws.amazon.com/global-accelerator/latest/dg/about-listeners.html#about-listeners-client-affinity */ export enum ClientAffinity { /** - * default affinity + * Route traffic based on the 5-tuple `(source IP, source port, destination IP, destination port, protocol)` */ NONE = 'NONE', + /** - * affinity by source IP + * Route traffic based on the 2-tuple `(source IP, destination IP)` + * + * The result is that multiple connections from the same client will be routed the same. */ SOURCE_IP = 'SOURCE_IP', } @@ -127,7 +143,7 @@ export class Listener extends cdk.Resource implements IListener { acceleratorArn: props.accelerator.acceleratorArn, portRanges: props.portRanges.map(m => ({ fromPort: m.fromPort, - toPort: m.toPort, + toPort: m.toPort ?? m.fromPort, })), protocol: props.protocol ?? ConnectionProtocol.TCP, clientAffinity: props.clientAffinity ?? ClientAffinity.NONE, @@ -135,6 +151,15 @@ export class Listener extends cdk.Resource implements IListener { this.listenerArn = resource.attrListenerArn; this.listenerName = props.listenerName ?? resource.logicalId; + } + /** + * Add a new endpoint group to this listener + */ + public addEndpointGroup(id: string, options: EndpointGroupOptions = {}) { + return new EndpointGroup(this, id, { + listener: this, + ...options, + }); } } diff --git a/packages/@aws-cdk/aws-globalaccelerator/package.json b/packages/@aws-cdk/aws-globalaccelerator/package.json index 1e98de7ee0bc8..f325ec000d862 100644 --- a/packages/@aws-cdk/aws-globalaccelerator/package.json +++ b/packages/@aws-cdk/aws-globalaccelerator/package.json @@ -74,7 +74,6 @@ "license": "Apache-2.0", "devDependencies": { "@aws-cdk/assert": "0.0.0", - "@aws-cdk/aws-elasticloadbalancingv2": "0.0.0", "cdk-integ-tools": "0.0.0", "cdk-build-tools": "0.0.0", "cfn2ts": "0.0.0", @@ -95,8 +94,8 @@ "engines": { "node": ">= 10.13.0 <13 || >=13.7.0" }, - "stability": "experimental", - "maturity": "experimental", + "stability": "stable", + "maturity": "stable", "awscdkio": { "announce": false }, diff --git a/packages/@aws-cdk/aws-globalaccelerator/test/globalaccelerator-security-group.test.ts b/packages/@aws-cdk/aws-globalaccelerator/test/globalaccelerator-security-group.test.ts index 20881d152f396..8101fa98acfba 100644 --- a/packages/@aws-cdk/aws-globalaccelerator/test/globalaccelerator-security-group.test.ts +++ b/packages/@aws-cdk/aws-globalaccelerator/test/globalaccelerator-security-group.test.ts @@ -1,7 +1,7 @@ import { expect, haveResource, ResourcePart } from '@aws-cdk/assert'; -import { Port } from '@aws-cdk/aws-ec2'; +import * as ec2 from '@aws-cdk/aws-ec2'; import * as ga from '../lib'; -import { testFixture, testFixtureAlb } from './util'; +import { testFixture } from './util'; test('custom resource exists', () => { // GIVEN @@ -19,7 +19,7 @@ test('custom resource exists', () => { const endpointGroup = new ga.EndpointGroup(stack, 'Group', { listener }); // WHEN - ga.AcceleratorSecurityGroup.fromVpc(stack, 'GlobalAcceleratorSG', vpc, endpointGroup); + endpointGroup.connectionsPeer('GlobalAcceleratorSG', vpc); // THEN expect(stack).to(haveResource('Custom::AWS', { @@ -45,7 +45,7 @@ test('custom resource exists', () => { InstallLatestAwsSdk: true, }, DependsOn: [ - 'GlobalAcceleratorSGCustomResourceCustomResourcePolicyF3294553', + 'GroupGlobalAcceleratorSGCustomResourceCustomResourcePolicy9C957AD2', 'GroupC77FDACD', ], }, ResourcePart.CompleteDefinition)); @@ -53,7 +53,7 @@ test('custom resource exists', () => { test('can create security group rule', () => { // GIVEN - const { stack, alb, vpc } = testFixtureAlb(); + const { stack, vpc } = testFixture(); const accelerator = new ga.Accelerator(stack, 'Accelerator'); const listener = new ga.Listener(stack, 'Listener', { accelerator, @@ -65,11 +65,12 @@ test('can create security group rule', () => { ], }); const endpointGroup = new ga.EndpointGroup(stack, 'Group', { listener }); - endpointGroup.addLoadBalancer('endpoint', alb); // WHEN - const sg = ga.AcceleratorSecurityGroup.fromVpc(stack, 'GlobalAcceleratorSG', vpc, endpointGroup); - alb.connections.allowFrom(sg, Port.tcp(443)); + const gaSg = endpointGroup.connectionsPeer('GlobalAcceleratorSG', vpc); + const instanceSg = new ec2.SecurityGroup(stack, 'SG', { vpc }); + const instanceConnections = new ec2.Connections({ securityGroups: [instanceSg] }); + instanceConnections.allowFrom(gaSg, ec2.Port.tcp(443)); // THEN expect(stack).to(haveResource('AWS::EC2::SecurityGroupIngress', { @@ -77,13 +78,13 @@ test('can create security group rule', () => { FromPort: 443, GroupId: { 'Fn::GetAtt': [ - 'ALBSecurityGroup8B8624F8', + 'SGADB53937', 'GroupId', ], }, SourceSecurityGroupId: { 'Fn::GetAtt': [ - 'GlobalAcceleratorSGCustomResourceC1DB5287', + 'GroupGlobalAcceleratorSGCustomResource0C8056E9', 'SecurityGroups.0.GroupId', ], }, diff --git a/packages/@aws-cdk/aws-globalaccelerator/test/globalaccelerator.test.ts b/packages/@aws-cdk/aws-globalaccelerator/test/globalaccelerator.test.ts index 72e1c79e586dd..ddbd69c269ca2 100644 --- a/packages/@aws-cdk/aws-globalaccelerator/test/globalaccelerator.test.ts +++ b/packages/@aws-cdk/aws-globalaccelerator/test/globalaccelerator.test.ts @@ -1,6 +1,5 @@ import { expect, haveResourceLike } from '@aws-cdk/assert'; -import * as ec2 from '@aws-cdk/aws-ec2'; -import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; +import { Duration } from '@aws-cdk/core'; import * as ga from '../lib'; import { testFixture } from './util'; @@ -52,6 +51,29 @@ test('create listener', () => { })); }); +test('toPort defaults to fromPort if left out', () => { + // GIVEN + const { stack } = testFixture(); + + // WHEN + const accelerator = new ga.Accelerator(stack, 'Accelerator'); + accelerator.addListener('Listener', { + portRanges: [ + { fromPort: 123 }, + ], + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::GlobalAccelerator::Listener', { + PortRanges: [ + { + FromPort: 123, + ToPort: 123, + }, + ], + })); +}); + test('create endpointgroup', () => { // GIVEN const { stack } = testFixture(); @@ -83,73 +105,75 @@ test('create endpointgroup', () => { })); }); -test('addEndpoint', () => { +test('endpointgroup region is the first endpoint\'s region', () => { // GIVEN - const { stack, vpc } = testFixture(); + const { stack } = testFixture(); // WHEN const accelerator = new ga.Accelerator(stack, 'Accelerator'); const listener = new ga.Listener(stack, 'Listener', { accelerator, - portRanges: [ - { - fromPort: 80, - toPort: 80, - }, - ], + portRanges: [{ fromPort: 80 }], }); - const endpointGroup = new ga.EndpointGroup(stack, 'Group', { listener }); - const instance = new ec2.Instance(stack, 'Instance', { - vpc, - machineImage: new ec2.AmazonLinuxImage(), - instanceType: new ec2.InstanceType('t3.small'), + listener.addEndpointGroup('Group', { + endpoints: [ + new ga.RawEndpoint({ + endpointId: 'x-123', + region: 'us-bla-5', + }), + ], }); - endpointGroup.addEndpoint('endpoint', instance.instanceId); // THEN expect(stack).to(haveResourceLike('AWS::GlobalAccelerator::EndpointGroup', { - EndpointConfigurations: [ - { - EndpointId: { - Ref: 'InstanceC1063A87', - }, - }, - ], + EndpointGroupRegion: 'us-bla-5', })); }); -test('addLoadBalancer', () => { +test('endpointgroup with all parameters', () => { // GIVEN - const { stack, vpc } = testFixture(); + const { stack } = testFixture(); // WHEN const accelerator = new ga.Accelerator(stack, 'Accelerator'); - const listener = new ga.Listener(stack, 'Listener', { - accelerator, - portRanges: [ + const listener = accelerator.addListener('Listener', { + portRanges: [{ fromPort: 80 }], + }); + listener.addEndpointGroup('Group', { + region: 'us-bla-5', + healthCheckInterval: Duration.seconds(10), + healthCheckPath: '/ping', + healthCheckPort: 123, + healthCheckProtocol: ga.HealthCheckProtocol.HTTPS, + healthCheckThreshold: 23, + trafficDialPercentage: 86, + portOverrides: [ { - fromPort: 80, - toPort: 80, + listenerPort: 80, + endpointPort: 8080, }, ], }); - const endpointGroup = new ga.EndpointGroup(stack, 'Group', { listener }); - const alb = new elbv2.ApplicationLoadBalancer(stack, 'ALB', { vpc, internetFacing: true }); - endpointGroup.addLoadBalancer('endpoint', alb); // THEN expect(stack).to(haveResourceLike('AWS::GlobalAccelerator::EndpointGroup', { - EndpointConfigurations: [ + EndpointGroupRegion: 'us-bla-5', + HealthCheckIntervalSeconds: 10, + HealthCheckPath: '/ping', + HealthCheckPort: 123, + HealthCheckProtocol: 'HTTPS', + PortOverrides: [ { - EndpointId: { - Ref: 'ALBAEE750D2', - }, + EndpointPort: 8080, + ListenerPort: 80, }, ], + ThresholdCount: 23, + TrafficDialPercentage: 86, })); }); -test('addElasticIpAddress', () => { +test('addEndpoint', () => { // GIVEN const { stack } = testFixture(); @@ -164,56 +188,26 @@ test('addElasticIpAddress', () => { }, ], }); - const endpointGroup = new ga.EndpointGroup(stack, 'Group', { listener }); - const eip = new ec2.CfnEIP(stack, 'ElasticIpAddress'); - endpointGroup.addElasticIpAddress('endpoint', eip); - - // THEN - expect(stack).to(haveResourceLike('AWS::GlobalAccelerator::EndpointGroup', { - EndpointConfigurations: [ - { - EndpointId: { - 'Fn::GetAtt': [ - 'ElasticIpAddress', - 'AllocationId', - ], - }, - }, - ], - })); -}); -test('addEc2Instance', () => { - // GIVEN - const { stack, vpc } = testFixture(); - // WHEN - const accelerator = new ga.Accelerator(stack, 'Accelerator'); - const listener = new ga.Listener(stack, 'Listener', { - accelerator, - portRanges: [ - { - fromPort: 80, - toPort: 80, - }, + listener.addEndpointGroup('Group', { + endpoints: [ + new ga.RawEndpoint({ + endpointId: 'i-123', + preserveClientIp: true, + weight: 30, + }), ], }); - const endpointGroup = new ga.EndpointGroup(stack, 'Group', { listener }); - const instance = new ec2.Instance(stack, 'Instance', { - vpc, - machineImage: new ec2.AmazonLinuxImage(), - instanceType: new ec2.InstanceType('t3.small'), - }); - endpointGroup.addEc2Instance('endpoint', instance); // THEN expect(stack).to(haveResourceLike('AWS::GlobalAccelerator::EndpointGroup', { EndpointConfigurations: [ { - EndpointId: { - Ref: 'InstanceC1063A87', - }, + EndpointId: 'i-123', + ClientIPPreservationEnabled: true, + Weight: 30, }, ], })); -}); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-globalaccelerator/test/util.ts b/packages/@aws-cdk/aws-globalaccelerator/test/util.ts index 9cf60a33a2064..0ad64f2329cf2 100644 --- a/packages/@aws-cdk/aws-globalaccelerator/test/util.ts +++ b/packages/@aws-cdk/aws-globalaccelerator/test/util.ts @@ -1,55 +1,10 @@ import * as ec2 from '@aws-cdk/aws-ec2'; -import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; import { App, Stack } from '@aws-cdk/core'; -import { Construct } from 'constructs'; export function testFixture() { - const { stack, app } = testFixtureNoVpc(); - const vpc = new ec2.Vpc(stack, 'VPC'); - - return { stack, vpc, app }; -} - -export function testFixtureNoVpc() { const app = new App(); const stack = new Stack(app, 'Stack'); - return { stack, app }; -} - -export function testFixtureAlb() { - const { stack, app, vpc } = testFixture(); - const alb = new elbv2.ApplicationLoadBalancer(stack, 'ALB', { vpc, internetFacing: true }); - - return { stack, app, alb, vpc }; -} - -export function testFixtureNlb() { - const { stack, app, vpc } = testFixture(); - const nlb = new elbv2.NetworkLoadBalancer(stack, 'NLB', { vpc, internetFacing: true }); - - return { stack, app, nlb }; -} - -export function testFixtureEip() { - const { stack, app } = testFixtureNoVpc(); - const eip = new ec2.CfnEIP(stack, 'ElasticIpAddress'); - - return { stack, app, eip }; -} - -export function testFixtureEc2() { - const { stack, app, vpc } = testFixture(); - const instance = new ec2.Instance(stack, 'Ec2', { - vpc, - machineImage: new ec2.AmazonLinuxImage(), - instanceType: new ec2.InstanceType('t3.small'), - }); - - return { stack, app, instance }; -} + const vpc = new ec2.Vpc(stack, 'VPC'); -export class TestStack extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - } -} + return { stack, vpc, app }; +} \ No newline at end of file diff --git a/packages/@aws-cdk/core/lib/resource.ts b/packages/@aws-cdk/core/lib/resource.ts index a828f0d6cec98..79d5542acc56e 100644 --- a/packages/@aws-cdk/core/lib/resource.ts +++ b/packages/@aws-cdk/core/lib/resource.ts @@ -90,6 +90,18 @@ export interface ResourceProps { * @default - the resource is in the same region as the stack it belongs to */ readonly region?: string; + + /** + * ARN to deduce region and account from + * + * The ARN is parsed and the account and region are taken from the ARN. + * This should be used for imported resources. + * + * Cannot be supplied together with either `account` or `region`. + * + * @default - take environment from `account`, `region` parameters, or use Stack environment. + */ + readonly environmentFromArn?: string; } /** @@ -126,12 +138,18 @@ export abstract class Resource extends CoreConstruct implements IResource { constructor(scope: Construct, id: string, props: ResourceProps = {}) { super(scope, id); + if ((props.account !== undefined || props.region !== undefined) && props.environmentFromArn !== undefined) { + throw new Error(`Supply at most one of 'account'/'region' (${props.account}/${props.region}) and 'environmentFromArn' (${props.environmentFromArn})`); + } + Object.defineProperty(this, RESOURCE_SYMBOL, { value: true }); this.stack = Stack.of(this); + + const parsedArn = props.environmentFromArn ? this.stack.parseArn(props.environmentFromArn) : undefined; this.env = { - account: props.account ?? this.stack.account, - region: props.region ?? this.stack.region, + account: props.account ?? parsedArn?.account ?? this.stack.account, + region: props.region ?? parsedArn?.region ?? this.stack.region, }; let physicalName = props.physicalName; diff --git a/packages/@aws-cdk/core/test/resource.test.ts b/packages/@aws-cdk/core/test/resource.test.ts index b883d11f8d752..aa8fbe74575fc 100644 --- a/packages/@aws-cdk/core/test/resource.test.ts +++ b/packages/@aws-cdk/core/test/resource.test.ts @@ -3,7 +3,7 @@ import { nodeunitShim, Test } from 'nodeunit-shim'; import { App, App as Root, CfnCondition, CfnDeletionPolicy, CfnResource, Construct, - Fn, RemovalPolicy, Stack, + Fn, RemovalPolicy, Resource, Stack, } from '../lib'; import { synthesize } from '../lib/private/synthesis'; import { toCloudFormation } from './util'; @@ -818,6 +818,19 @@ nodeunitShim({ }, }); +test('Resource can get account and Region from ARN', () => { + const stack = new Stack(); + + // WHEN + const resource = new TestResource(stack, 'Resource', { + environmentFromArn: 'arn:partition:service:region:account:relative-id', + }); + + // THEN + expect(resource.env.account).toEqual('account'); + expect(resource.env.region).toEqual('region'); +}); + interface CounterProps { Count: number; } @@ -887,3 +900,8 @@ class CustomizableResource extends CfnResource { return cleanProps; } } + +/** + * Because Resource is abstract + */ +class TestResource extends Resource {} \ No newline at end of file diff --git a/packages/aws-cdk-lib/package.json b/packages/aws-cdk-lib/package.json index 5bd2c80c966f9..be2c9d0b7b87f 100644 --- a/packages/aws-cdk-lib/package.json +++ b/packages/aws-cdk-lib/package.json @@ -191,6 +191,7 @@ "@aws-cdk/aws-fsx": "0.0.0", "@aws-cdk/aws-gamelift": "0.0.0", "@aws-cdk/aws-globalaccelerator": "0.0.0", + "@aws-cdk/aws-globalaccelerator-endpoints": "0.0.0", "@aws-cdk/aws-glue": "0.0.0", "@aws-cdk/aws-greengrass": "0.0.0", "@aws-cdk/aws-greengrassv2": "0.0.0", diff --git a/packages/decdk/package.json b/packages/decdk/package.json index 27978f6c4d451..62d20f552353b 100644 --- a/packages/decdk/package.json +++ b/packages/decdk/package.json @@ -112,6 +112,7 @@ "@aws-cdk/aws-fsx": "0.0.0", "@aws-cdk/aws-gamelift": "0.0.0", "@aws-cdk/aws-globalaccelerator": "0.0.0", + "@aws-cdk/aws-globalaccelerator-endpoints": "0.0.0", "@aws-cdk/aws-glue": "0.0.0", "@aws-cdk/aws-greengrass": "0.0.0", "@aws-cdk/aws-greengrassv2": "0.0.0", diff --git a/packages/monocdk/package.json b/packages/monocdk/package.json index 7f1f6da5555b1..fc542a6dee2b9 100644 --- a/packages/monocdk/package.json +++ b/packages/monocdk/package.json @@ -196,6 +196,7 @@ "@aws-cdk/aws-fsx": "0.0.0", "@aws-cdk/aws-gamelift": "0.0.0", "@aws-cdk/aws-globalaccelerator": "0.0.0", + "@aws-cdk/aws-globalaccelerator-endpoints": "0.0.0", "@aws-cdk/aws-glue": "0.0.0", "@aws-cdk/aws-greengrass": "0.0.0", "@aws-cdk/aws-greengrassv2": "0.0.0",