From c82f16fd9129c4b7796b26f8c38bc3aff16549d2 Mon Sep 17 00:00:00 2001 From: "Rafael Z. Kineippe" Date: Mon, 10 Feb 2020 15:33:03 +0100 Subject: [PATCH] fix(elasticloadbalancingv2): logAccessLogs in Base Load Balancer Moving the method logAccessLogs to the Base Load Balancer, so both types of Load Balancer (Network and Application) can use the method. closes #3794 --- .../lib/alb/application-load-balancer.ts | 59 +--------- .../lib/shared/base-load-balancer.ts | 59 +++++++++- .../test/nlb/test.load-balancer.ts | 105 +++++++++++++++++- 3 files changed, 163 insertions(+), 60 deletions(-) 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 33d8cf8557895..0886486a89458 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts @@ -1,8 +1,6 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as ec2 from '@aws-cdk/aws-ec2'; -import * as iam from '@aws-cdk/aws-iam'; -import * as s3 from '@aws-cdk/aws-s3'; -import { Construct, Duration, Lazy, Resource, Stack, Token } from '@aws-cdk/core'; +import { Construct, Duration, Lazy, Resource } from '@aws-cdk/core'; import { BaseLoadBalancer, BaseLoadBalancerProps, ILoadBalancerV2 } from '../shared/base-load-balancer'; import { IpAddressType } from '../shared/enums'; import { ApplicationListener, BaseApplicationListenerProps } from './application-listener'; @@ -78,34 +76,6 @@ export class ApplicationLoadBalancer extends BaseLoadBalancer implements IApplic if (props.idleTimeout !== undefined) { this.setAttribute('idle_timeout.timeout_seconds', props.idleTimeout.toSeconds().toString()); } } - /** - * Enable access logging for this load balancer. - * - * A region must be specified on the stack containing the load balancer; you cannot enable logging on - * environment-agnostic stacks. See https://docs.aws.amazon.com/cdk/latest/guide/environments.html - */ - public logAccessLogs(bucket: s3.IBucket, prefix?: string) { - this.setAttribute('access_logs.s3.enabled', 'true'); - this.setAttribute('access_logs.s3.bucket', bucket.bucketName.toString()); - this.setAttribute('access_logs.s3.prefix', prefix); - - const region = Stack.of(this).region; - if (Token.isUnresolved(region)) { - throw new Error(`Region is required to enable ELBv2 access logging`); - } - - const account = ELBV2_ACCOUNTS[region]; - if (!account) { - throw new Error(`Cannot enable access logging; don't know ELBv2 account for region ${region}`); - } - - prefix = prefix || ''; - bucket.grantPut(new iam.AccountPrincipal(account), `${(prefix ? prefix + "/" : "")}AWSLogs/${Stack.of(this).account}/*`); - - // make sure the bucket's policy is created before the ALB (see https://github.com/aws/aws-cdk/issues/1633) - this.node.addDependency(bucket); - } - /** * Add a new listener to this load balancer */ @@ -530,33 +500,6 @@ export interface ApplicationLoadBalancerAttributes { readonly securityGroupAllowsAllOutbound?: boolean; } -// https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-access-logs.html#access-logging-bucket-permissions -const ELBV2_ACCOUNTS: { [region: string]: string } = { - 'us-east-1': '127311923021', - 'us-east-2': '033677994240', - 'us-west-1': '027434742980', - 'us-west-2': '797873946194', - 'ca-central-1': '985666609251', - 'eu-central-1': '054676820928', - 'eu-west-1': '156460612806', - 'eu-west-2': '652711504416', - 'eu-west-3': '009996457667', - 'eu-north-1': '897822967062', - 'ap-east-1': '754344448648', - 'ap-northeast-1': '582318560864', - 'ap-northeast-2': '600734575887', - 'ap-northeast-3': '383597477331', - 'ap-southeast-1': '114774131450', - 'ap-southeast-2': '783225319266', - 'ap-south-1': '718504428378', - 'me-south-1': '076674570225', - 'sa-east-1': '507241528517', - 'us-gov-west-1': '048591011584', - 'us-gov-east-1': '190560391635', - 'cn-north-1': '638102146993', - 'cn-northwest-1': '037604701340', -}; - /** * An ApplicationLoadBalancer that has been defined elsewhere */ diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts index 0a4cb0ac3eaeb..75082a6581f3f 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts @@ -1,5 +1,7 @@ import * as ec2 from '@aws-cdk/aws-ec2'; -import { Construct, IResource, Lazy, Resource } from '@aws-cdk/core'; +import * as iam from '@aws-cdk/aws-iam'; +import * as s3 from '@aws-cdk/aws-s3'; +import { Construct, IResource, Lazy, Resource, Stack, Token } from '@aws-cdk/core'; import { CfnLoadBalancer } from '../elasticloadbalancingv2.generated'; import { Attributes, ifUndefined, renderAttributes } from './util'; @@ -156,6 +158,34 @@ export abstract class BaseLoadBalancer extends Resource { this.loadBalancerSecurityGroups = resource.attrSecurityGroups; } + /** + * Enable access logging for this load balancer. + * + * A region must be specified on the stack containing the load balancer; you cannot enable logging on + * environment-agnostic stacks. See https://docs.aws.amazon.com/cdk/latest/guide/environments.html + */ + public logAccessLogs(bucket: s3.IBucket, prefix?: string) { + this.setAttribute('access_logs.s3.enabled', 'true'); + this.setAttribute('access_logs.s3.bucket', bucket.bucketName.toString()); + this.setAttribute('access_logs.s3.prefix', prefix); + + const region = Stack.of(this).region; + if (Token.isUnresolved(region)) { + throw new Error(`Region is required to enable ELBv2 access logging`); + } + + const account = ELBV2_ACCOUNTS[region]; + if (!account) { + throw new Error(`Cannot enable access logging; don't know ELBv2 account for region ${region}`); + } + + prefix = prefix || ''; + bucket.grantPut(new iam.AccountPrincipal(account), `${(prefix ? prefix + "/" : "")}AWSLogs/${Stack.of(this).account}/*`); + + // make sure the bucket's policy is created before the ALB (see https://github.com/aws/aws-cdk/issues/1633) + this.node.addDependency(bucket); + } + /** * Set a non-standard attribute on the load balancer * @@ -172,3 +202,30 @@ export abstract class BaseLoadBalancer extends Resource { this.setAttribute(key, undefined); } } + +// https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-access-logs.html#access-logging-bucket-permissions +const ELBV2_ACCOUNTS: { [region: string]: string } = { + 'us-east-1': '127311923021', + 'us-east-2': '033677994240', + 'us-west-1': '027434742980', + 'us-west-2': '797873946194', + 'ca-central-1': '985666609251', + 'eu-central-1': '054676820928', + 'eu-west-1': '156460612806', + 'eu-west-2': '652711504416', + 'eu-west-3': '009996457667', + 'eu-north-1': '897822967062', + 'ap-east-1': '754344448648', + 'ap-northeast-1': '582318560864', + 'ap-northeast-2': '600734575887', + 'ap-northeast-3': '383597477331', + 'ap-southeast-1': '114774131450', + 'ap-southeast-2': '783225319266', + 'ap-south-1': '718504428378', + 'me-south-1': '076674570225', + 'sa-east-1': '507241528517', + 'us-gov-west-1': '048591011584', + 'us-gov-east-1': '190560391635', + 'cn-north-1': '638102146993', + 'cn-northwest-1': '037604701340', +}; diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/test.load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/test.load-balancer.ts index 5c0046e186013..79d3808157f4b 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/test.load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/test.load-balancer.ts @@ -1,5 +1,6 @@ -import { expect, haveResource } from '@aws-cdk/assert'; +import { expect, haveResource, ResourcePart } from '@aws-cdk/assert'; import * as ec2 from '@aws-cdk/aws-ec2'; +import * as s3 from '@aws-cdk/aws-s3'; import * as cdk from '@aws-cdk/core'; import { Test } from 'nodeunit'; import * as elbv2 from '../../lib'; @@ -74,6 +75,108 @@ export = { test.done(); }, + 'Access logging'(test: Test) { + // GIVEN + const stack = new cdk.Stack(undefined, undefined, { env: { region: 'us-east-1' } }); + const vpc = new ec2.Vpc(stack, 'Stack'); + const bucket = new s3.Bucket(stack, 'AccessLoggingBucket'); + const lb = new elbv2.NetworkLoadBalancer(stack, 'LB', { vpc }); + + // WHEN + lb.logAccessLogs(bucket); + + // THEN + + // verify that the LB attributes reference the bucket + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::LoadBalancer', { + LoadBalancerAttributes: [ + { + Key: "access_logs.s3.enabled", + Value: "true" + }, + { + Key: "access_logs.s3.bucket", + Value: { Ref: "AccessLoggingBucketA6D88F29" } + } + ], + })); + + // verify the bucket policy allows the ALB to put objects in the bucket + expect(stack).to(haveResource('AWS::S3::BucketPolicy', { + PolicyDocument: { + Version: '2012-10-17', + Statement: [ + { + Action: ["s3:PutObject*", "s3:Abort*"], + Effect: 'Allow', + Principal: { AWS: { "Fn::Join": ["", ["arn:", { Ref: "AWS::Partition" }, ":iam::127311923021:root"]] } }, + Resource: { + "Fn::Join": ["", [{ "Fn::GetAtt": ["AccessLoggingBucketA6D88F29", "Arn"] }, "/AWSLogs/", + { Ref: "AWS::AccountId" }, "/*"]] + } + } + ] + } + })); + + // verify the ALB depends on the bucket *and* the bucket policy + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::LoadBalancer', { + DependsOn: ['AccessLoggingBucketPolicy700D7CC6', 'AccessLoggingBucketA6D88F29'] + }, ResourcePart.CompleteDefinition)); + + test.done(); + }, + + 'access logging with prefix'(test: Test) { + // GIVEN + const stack = new cdk.Stack(undefined, undefined, { env: { region: 'us-east-1' } }); + const vpc = new ec2.Vpc(stack, 'Stack'); + const bucket = new s3.Bucket(stack, 'AccessLoggingBucket'); + const lb = new elbv2.NetworkLoadBalancer(stack, 'LB', { vpc }); + + // WHEN + lb.logAccessLogs(bucket, 'prefix-of-access-logs'); + + // THEN + // verify that the LB attributes reference the bucket + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::LoadBalancer', { + LoadBalancerAttributes: [ + { + Key: "access_logs.s3.enabled", + Value: "true" + }, + { + Key: "access_logs.s3.bucket", + Value: { Ref: "AccessLoggingBucketA6D88F29" } + }, + { + Key: "access_logs.s3.prefix", + Value: "prefix-of-access-logs" + } + ], + })); + + // verify the bucket policy allows the ALB to put objects in the bucket + expect(stack).to(haveResource('AWS::S3::BucketPolicy', { + PolicyDocument: { + Version: '2012-10-17', + Statement: [ + { + Action: ["s3:PutObject*", "s3:Abort*"], + Effect: 'Allow', + Principal: { AWS: { "Fn::Join": ["", ["arn:", { Ref: "AWS::Partition" }, ":iam::127311923021:root"]] } }, + Resource: { + "Fn::Join": ["", [{ "Fn::GetAtt": ["AccessLoggingBucketA6D88F29", "Arn"] }, "/prefix-of-access-logs/AWSLogs/", + { Ref: "AWS::AccountId" }, "/*"]] + } + } + ] + } + })); + + test.done(); + }, + 'loadBalancerName'(test: Test) { // GIVEN const stack = new cdk.Stack();