From 067c4a5740dfdcc6c383b85bbbe65798e02b2431 Mon Sep 17 00:00:00 2001 From: kazuho cryer-shinozuka Date: Tue, 9 Apr 2024 07:56:42 +0900 Subject: [PATCH] feat(elasticloadbalancingv2): application load balancer attributes (#29586) ### Issue # (if applicable) Closes #29585. ### Reason for this change ALB supports some attributes that is not configurable from CDK - `routing.http.preserve_host_header.enabled` - `routing.http.x_amzn_tls_version_and_cipher_suite.enabled` - `routing.http.xff_client_port.enabled` - `routing.http.xff_header_processing.mode` - `waf.fail_open.enabled` ### Description of changes Added some props to `ApplicationLoadBalancerProps`. - `preserveHostHeader` - `xAmznTlsVersionAndCipherSuiteHeaders` - `preserveXffClientPort` - `xffHeaderProcessingMode` - `wafFailOpen` ### Description of how you validated changes Added both unit and integ tests. ### Checklist - [x] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../aws-cdk-elbv2-integ.assets.json | 4 +- .../aws-cdk-elbv2-integ.template.json | 20 +++++ .../manifest.json | 2 +- .../tree.json | 20 +++++ .../test/integ.alb.attributes.ts | 5 ++ .../aws-elasticloadbalancingv2/README.md | 17 ++++- .../lib/alb/application-load-balancer.ts | 75 +++++++++++++++++++ .../test/alb/load-balancer.test.ts | 25 +++++++ 8 files changed, 164 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.attributes.js.snapshot/aws-cdk-elbv2-integ.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.attributes.js.snapshot/aws-cdk-elbv2-integ.assets.json index 61e205aa8a505..852680acc8924 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.attributes.js.snapshot/aws-cdk-elbv2-integ.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.attributes.js.snapshot/aws-cdk-elbv2-integ.assets.json @@ -1,7 +1,7 @@ { "version": "36.0.0", "files": { - "94c6c20cc1d7906c1b2328a47826a615c5175c928769994769ea4f60c79f394c": { + "9619c7d4a819d830abc8dcf2a08ede5e397119d7a6702f8903e24e89fdfe6a7d": { "source": { "path": "aws-cdk-elbv2-integ.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "94c6c20cc1d7906c1b2328a47826a615c5175c928769994769ea4f60c79f394c.json", + "objectKey": "9619c7d4a819d830abc8dcf2a08ede5e397119d7a6702f8903e24e89fdfe6a7d.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.attributes.js.snapshot/aws-cdk-elbv2-integ.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.attributes.js.snapshot/aws-cdk-elbv2-integ.template.json index 4c5482a79d6f9..694062bc3f358 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.attributes.js.snapshot/aws-cdk-elbv2-integ.template.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.attributes.js.snapshot/aws-cdk-elbv2-integ.template.json @@ -415,6 +415,26 @@ "Key": "routing.http.desync_mitigation_mode", "Value": "defensive" }, + { + "Key": "routing.http.preserve_host_header.enabled", + "Value": "true" + }, + { + "Key": "routing.http.x_amzn_tls_version_and_cipher_suite.enabled", + "Value": "true" + }, + { + "Key": "routing.http.xff_client_port.enabled", + "Value": "true" + }, + { + "Key": "routing.http.xff_header_processing.mode", + "Value": "preserve" + }, + { + "Key": "waf.fail_open.enabled", + "Value": "true" + }, { "Key": "client_keep_alive.seconds", "Value": "1000" diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.attributes.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.attributes.js.snapshot/manifest.json index cdb4b77174ab6..bdeaf785fb193 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.attributes.js.snapshot/manifest.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.attributes.js.snapshot/manifest.json @@ -18,7 +18,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/94c6c20cc1d7906c1b2328a47826a615c5175c928769994769ea4f60c79f394c.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/9619c7d4a819d830abc8dcf2a08ede5e397119d7a6702f8903e24e89fdfe6a7d.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.attributes.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.attributes.js.snapshot/tree.json index bb57d65671ade..e58f2dd077946 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.attributes.js.snapshot/tree.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.attributes.js.snapshot/tree.json @@ -682,6 +682,26 @@ "key": "routing.http.desync_mitigation_mode", "value": "defensive" }, + { + "key": "routing.http.preserve_host_header.enabled", + "value": "true" + }, + { + "key": "routing.http.x_amzn_tls_version_and_cipher_suite.enabled", + "value": "true" + }, + { + "key": "routing.http.xff_client_port.enabled", + "value": "true" + }, + { + "key": "routing.http.xff_header_processing.mode", + "value": "preserve" + }, + { + "key": "waf.fail_open.enabled", + "value": "true" + }, { "key": "client_keep_alive.seconds", "value": "1000" diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.attributes.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.attributes.ts index cc6bdeb1e3683..a5d517c5866b5 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.attributes.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.attributes.ts @@ -20,6 +20,11 @@ new elbv2.ApplicationLoadBalancer(stack, 'LB', { dropInvalidHeaderFields: true, desyncMitigationMode: elbv2.DesyncMitigationMode.DEFENSIVE, clientKeepAlive: cdk.Duration.seconds(1000), + preserveHostHeader: true, + xAmznTlsVersionAndCipherSuiteHeaders: true, + preserveXffClientPort: true, + xffHeaderProcessingMode: elbv2.XffHeaderProcessingMode.PRESERVE, + wafFailOpen: true, }); new elbv2.ApplicationLoadBalancer(stack, 'DesyncMitigationModeMonitor', { diff --git a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/README.md b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/README.md index 401eb132b4840..34978410e072b 100644 --- a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/README.md +++ b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/README.md @@ -228,7 +228,22 @@ const lb = new elbv2.ApplicationLoadBalancer(this, 'LB', { crossZoneEnabled: true, // Whether the load balancer blocks traffic through the Internet Gateway (IGW). - denyAllIgwTraffic: false + denyAllIgwTraffic: false, + + // Whether to preserve host header in the request to the target + preserveHostHeader: true, + + // Whether to add the TLS information header to the request + xAmznTlsVersionAndCipherSuiteHeaders: true, + + // Whether the X-Forwarded-For header should preserve the source port + preserveXffClientPort: true, + + // The processing mode for X-Forwarded-For headers + xffHeaderProcessingMode: elbv2.XffHeaderProcessingMode.APPEND, + + // Whether to allow a load balancer to route requests to targets if it is unable to forward the request to AWS WAF. + wafFailOpen: true, }); ``` diff --git a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts index 4d7bbd7c38c4a..3182ffcb78cf7 100644 --- a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts +++ b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts @@ -16,6 +16,8 @@ import { parseLoadBalancerFullName } from '../shared/util'; /** * Properties for defining an Application Load Balancer + * + * @see https://docs.aws.amazon.com/elasticloadbalancing/latest/application/application-load-balancers.html#load-balancer-attributes */ export interface ApplicationLoadBalancerProps extends BaseLoadBalancerProps { /** @@ -68,6 +70,74 @@ export interface ApplicationLoadBalancerProps extends BaseLoadBalancerProps { * @default - Duration.seconds(3600) */ readonly clientKeepAlive?: Duration; + + /** + * Indicates whether the Application Load Balancer should preserve the host header in the HTTP request + * and send it to the target without any change. + * + * @default false + */ + readonly preserveHostHeader?: boolean; + + /** + * Indicates whether the two headers (x-amzn-tls-version and x-amzn-tls-cipher-suite), + * which contain information about the negotiated TLS version and cipher suite, + * are added to the client request before sending it to the target. + * + * The x-amzn-tls-version header has information about the TLS protocol version negotiated with the client, + * and the x-amzn-tls-cipher-suite header has information about the cipher suite negotiated with the client. + * + * Both headers are in OpenSSL format. + * + * @default false + */ + readonly xAmznTlsVersionAndCipherSuiteHeaders?: boolean; + + /** + * Indicates whether the X-Forwarded-For header should preserve the source port + * that the client used to connect to the load balancer. + * + * @default false + */ + readonly preserveXffClientPort?: boolean; + + /** + * Enables you to modify, preserve, or remove the X-Forwarded-For header in the HTTP request + * before the Application Load Balancer sends the request to the target. + * + * @default XffHeaderProcessingMode.APPEND + */ + readonly xffHeaderProcessingMode?: XffHeaderProcessingMode; + + /** + * Indicates whether to allow a WAF-enabled load balancer to route requests to targets + * if it is unable to forward the request to AWS WAF. + * + * @default false + */ + readonly wafFailOpen?: boolean; +} + +/** + * Processing mode of the X-Forwarded-For header in the HTTP request + * before the Application Load Balancer sends the request to the target. + */ +export enum XffHeaderProcessingMode { + /** + * Application Load Balancer adds the client IP address (of the last hop) to the X-Forwarded-For header + * in the HTTP request before it sends it to targets. + */ + APPEND = 'append', + /** + * Application Load Balancer preserves the X-Forwarded-For header in the HTTP request, + * and sends it to targets without any change. + */ + PRESERVE = 'preserve', + /** + * Application Load Balancer removes the X-Forwarded-For header + * in the HTTP request before it sends it to targets. + */ + REMOVE = 'remove', } /** @@ -129,6 +199,11 @@ export class ApplicationLoadBalancer extends BaseLoadBalancer implements IApplic if (props.idleTimeout !== undefined) { this.setAttribute('idle_timeout.timeout_seconds', props.idleTimeout.toSeconds().toString()); } if (props.dropInvalidHeaderFields) {this.setAttribute('routing.http.drop_invalid_header_fields.enabled', 'true'); } if (props.desyncMitigationMode !== undefined) {this.setAttribute('routing.http.desync_mitigation_mode', props.desyncMitigationMode); } + if (props.preserveHostHeader) { this.setAttribute('routing.http.preserve_host_header.enabled', 'true'); } + if (props.xAmznTlsVersionAndCipherSuiteHeaders) { this.setAttribute('routing.http.x_amzn_tls_version_and_cipher_suite.enabled', 'true'); } + if (props.preserveXffClientPort) { this.setAttribute('routing.http.xff_client_port.enabled', 'true'); } + if (props.xffHeaderProcessingMode !== undefined) { this.setAttribute('routing.http.xff_header_processing.mode', props.xffHeaderProcessingMode); } + if (props.wafFailOpen) { this.setAttribute('waf.fail_open.enabled', 'true'); } if (props.clientKeepAlive !== undefined) { const clientKeepAliveInMillis = props.clientKeepAlive.toMilliseconds(); if (clientKeepAliveInMillis < 1000) { diff --git a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/load-balancer.test.ts b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/load-balancer.test.ts index b9fd54bd7ce95..c420c1c0c5a89 100644 --- a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/load-balancer.test.ts +++ b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/load-balancer.test.ts @@ -85,6 +85,11 @@ describe('tests', () => { dropInvalidHeaderFields: true, clientKeepAlive: cdk.Duration.seconds(200), denyAllIgwTraffic: true, + preserveHostHeader: true, + xAmznTlsVersionAndCipherSuiteHeaders: true, + preserveXffClientPort: true, + xffHeaderProcessingMode: elbv2.XffHeaderProcessingMode.PRESERVE, + wafFailOpen: true, }); // THEN @@ -110,6 +115,26 @@ describe('tests', () => { Key: 'routing.http.drop_invalid_header_fields.enabled', Value: 'true', }, + { + Key: 'routing.http.preserve_host_header.enabled', + Value: 'true', + }, + { + Key: 'routing.http.x_amzn_tls_version_and_cipher_suite.enabled', + Value: 'true', + }, + { + Key: 'routing.http.xff_client_port.enabled', + Value: 'true', + }, + { + Key: 'routing.http.xff_header_processing.mode', + Value: 'preserve', + }, + { + Key: 'waf.fail_open.enabled', + Value: 'true', + }, { Key: 'client_keep_alive.seconds', Value: '200',