From 5009ec39e21f0bc2ea05c87eeadb1cf4a1b2d83e Mon Sep 17 00:00:00 2001 From: serahisaactho Date: Tue, 2 Nov 2021 22:43:43 +0000 Subject: [PATCH 1/4] feat(apigatewayv2): mTLS support --- packages/@aws-cdk/aws-apigatewayv2/README.md | 22 +++++++ .../lib/common/domain-name.ts | 37 +++++++++++ .../@aws-cdk/aws-apigatewayv2/package.json | 2 + .../test/http/domain-name.test.ts | 63 +++++++++++++++++++ 4 files changed, 124 insertions(+) diff --git a/packages/@aws-cdk/aws-apigatewayv2/README.md b/packages/@aws-cdk/aws-apigatewayv2/README.md index cf0a2ab0552c7..70f7dda76432e 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2/README.md @@ -34,6 +34,7 @@ Higher level constructs for Websocket APIs | ![Experimental](https://img.shields - [Cross Origin Resource Sharing (CORS)](#cross-origin-resource-sharing-cors) - [Publishing HTTP APIs](#publishing-http-apis) - [Custom Domain](#custom-domain) + - [Mutual TLS](#mutual-tls-mtls) - [Managing access](#managing-access) - [Metrics](#metrics) - [VPC Link](#vpc-link) @@ -230,6 +231,27 @@ You can retrieve the full domain URL with mapping key using the `domainUrl` prop const demoDomainUrl = apiDemo.defaultStage.domainUrl; // returns "https://example.com/demo" ``` +## Mutual TLS (mTLS) + +Mutual TLS can be configured to limit access to your API based by using client certificates instead of (or as an extension of) using authorization headers. + +```ts +const certArn = 'arn:aws:acm:us-east-1:111111111111:certificate'; +const domainName = 'example.com'; + +new DomainName(stack, 'DomainName', { + domainName, + certificate: Certificate.fromCertificateArn(stack, 'cert', certArn), + mtls: { + bucket, + key: 'someca.pem', + version: 'version', + }, +}) +``` + +Instructions for configuring your trust store can be found [here](https://aws.amazon.com/blogs/compute/introducing-mutual-tls-authentication-for-amazon-api-gateway/) + ### Managing access API Gateway supports multiple mechanisms for [controlling and managing access to your HTTP diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/common/domain-name.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/common/domain-name.ts index 6b1123512c678..e550a21f915f3 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/common/domain-name.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/common/domain-name.ts @@ -1,4 +1,5 @@ import { ICertificate } from '@aws-cdk/aws-certificatemanager'; +import { IBucket } from '@aws-cdk/aws-s3'; import { IResource, Resource, Token } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnDomainName, CfnDomainNameProps } from '../apigatewayv2.generated'; @@ -59,6 +60,32 @@ export interface DomainNameProps { * The ACM certificate for this domain name */ readonly certificate: ICertificate; + /** + * The mutual TLS authentication configuration for a custom domain name. + * @default - mTLS is not configured. + */ + readonly mtls?: MTLSConfig +} + +/** + * The mTLS authentication configuration for a custom domain name. + */ +export interface MTLSConfig { + /** + * The bucket that the trust store is hosted in. + */ + readonly bucket: IBucket; + /** + * The key in S3 to look at for the trust store + */ + readonly key: string; + + /** + * The version of the S3 object that contains your truststore. + * To specify a version, you must have versioning enabled for the S3 bucket. + * @default - latest version + */ + readonly version?: string; } /** @@ -88,6 +115,7 @@ export class DomainName extends Resource implements IDomainName { throw new Error('empty string for domainName not allowed'); } + const mtlsConfig = this.configureMTLS(props.mtls); const domainNameProps: CfnDomainNameProps = { domainName: props.domainName, domainNameConfigurations: [ @@ -96,10 +124,19 @@ export class DomainName extends Resource implements IDomainName { endpointType: 'REGIONAL', }, ], + mutualTlsAuthentication: mtlsConfig, }; const resource = new CfnDomainName(this, 'Resource', domainNameProps); this.name = resource.ref; this.regionalDomainName = Token.asString(resource.getAtt('RegionalDomainName')); this.regionalHostedZoneId = Token.asString(resource.getAtt('RegionalHostedZoneId')); } + + private configureMTLS(mtlsConfig?: MTLSConfig): CfnDomainName.MutualTlsAuthenticationProperty | undefined { + if (!mtlsConfig) return undefined; + return { + truststoreUri: mtlsConfig.bucket.s3UrlForObject(mtlsConfig.key), + truststoreVersion: mtlsConfig.version, + }; + } } diff --git a/packages/@aws-cdk/aws-apigatewayv2/package.json b/packages/@aws-cdk/aws-apigatewayv2/package.json index 349c1ab9517cf..7a68282dd6dda 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/package.json +++ b/packages/@aws-cdk/aws-apigatewayv2/package.json @@ -89,6 +89,7 @@ "@aws-cdk/aws-cloudwatch": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", + "@aws-cdk/aws-s3": "0.0.0", "@aws-cdk/core": "0.0.0", "constructs": "^3.3.69" }, @@ -97,6 +98,7 @@ "@aws-cdk/aws-cloudwatch": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", + "@aws-cdk/aws-s3": "0.0.0", "@aws-cdk/core": "0.0.0", "constructs": "^3.3.69" }, diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/http/domain-name.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/http/domain-name.test.ts index 2d0d856c7ae15..8ea450257f886 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/http/domain-name.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/http/domain-name.test.ts @@ -1,5 +1,6 @@ import { Template } from '@aws-cdk/assertions'; import { Certificate } from '@aws-cdk/aws-certificatemanager'; +import { Bucket } from '@aws-cdk/aws-s3'; import { Stack } from '@aws-cdk/core'; import { DomainName, HttpApi } from '../../lib'; @@ -168,4 +169,66 @@ describe('DomainName', () => { expect(t).toThrow('defaultDomainMapping not supported with createDefaultStage disabled'); }); + + test('accepts a mutual TLS configuration', () => { + // GIVEN + const stack = new Stack(); + const bucket = Bucket.fromBucketName(stack, 'testBucket', 'exampleBucket'); + + // WHEN + new DomainName(stack, 'DomainName', { + domainName, + certificate: Certificate.fromCertificateArn(stack, 'cert', certArn), + mtls: { + bucket, + key: 'someca.pem', + }, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::DomainName', { + DomainName: 'example.com', + DomainNameConfigurations: [ + { + CertificateArn: 'arn:aws:acm:us-east-1:111111111111:certificate', + EndpointType: 'REGIONAL', + }, + ], + MutualTlsAuthentication: { + TruststoreUri: 's3://exampleBucket/someca.pem', + }, + }); + }); + + test('mTLS should allow versions to be set on the s3 bucket', () => { + // GIVEN + const stack = new Stack(); + const bucket = Bucket.fromBucketName(stack, 'testBucket', 'exampleBucket'); + + // WHEN + new DomainName(stack, 'DomainName', { + domainName, + certificate: Certificate.fromCertificateArn(stack, 'cert', certArn), + mtls: { + bucket, + key: 'someca.pem', + version: 'version', + }, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::DomainName', { + DomainName: 'example.com', + DomainNameConfigurations: [ + { + CertificateArn: 'arn:aws:acm:us-east-1:111111111111:certificate', + EndpointType: 'REGIONAL', + }, + ], + MutualTlsAuthentication: { + TruststoreUri: 's3://exampleBucket/someca.pem', + TruststoreVersion: 'version', + }, + }); + }); }); From 83d2ae60d0f357f9c363eb9200e552ee6d2159e8 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Fri, 5 Nov 2021 16:55:44 +0000 Subject: [PATCH 2/4] Update packages/@aws-cdk/aws-apigatewayv2/README.md --- packages/@aws-cdk/aws-apigatewayv2/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/README.md b/packages/@aws-cdk/aws-apigatewayv2/README.md index 70f7dda76432e..c0f0fd3d7805a 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2/README.md @@ -246,7 +246,7 @@ new DomainName(stack, 'DomainName', { bucket, key: 'someca.pem', version: 'version', - }, + }, }) ``` From 86bfe240ff083e037bc3d6abfda246ce265d314a Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Fri, 5 Nov 2021 16:58:20 +0000 Subject: [PATCH 3/4] Update packages/@aws-cdk/aws-apigatewayv2/README.md --- packages/@aws-cdk/aws-apigatewayv2/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/@aws-cdk/aws-apigatewayv2/README.md b/packages/@aws-cdk/aws-apigatewayv2/README.md index c0f0fd3d7805a..02c86a7b67e3d 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2/README.md @@ -236,8 +236,10 @@ const demoDomainUrl = apiDemo.defaultStage.domainUrl; // returns "https://exampl Mutual TLS can be configured to limit access to your API based by using client certificates instead of (or as an extension of) using authorization headers. ```ts +import * as s3 from '@aws-cdk/aws-s3'; const certArn = 'arn:aws:acm:us-east-1:111111111111:certificate'; const domainName = 'example.com'; +const bucket = new s3.Bucket.fromBucketName(stack, 'TrustStoreBucket', ...); new DomainName(stack, 'DomainName', { domainName, From 2cadfec0a20f0a4e405f26b6eff19cea6c32e3ce Mon Sep 17 00:00:00 2001 From: serahisaactho Date: Mon, 8 Nov 2021 20:04:47 +0000 Subject: [PATCH 4/4] feat(apigatewayv2): http api - mTLS support - Fix test bucket names --- .../aws-apigatewayv2/test/http/domain-name.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/http/domain-name.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/http/domain-name.test.ts index 8ea450257f886..9c60f7b5196e3 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/http/domain-name.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/http/domain-name.test.ts @@ -173,7 +173,7 @@ describe('DomainName', () => { test('accepts a mutual TLS configuration', () => { // GIVEN const stack = new Stack(); - const bucket = Bucket.fromBucketName(stack, 'testBucket', 'exampleBucket'); + const bucket = Bucket.fromBucketName(stack, 'testBucket', 'example-bucket'); // WHEN new DomainName(stack, 'DomainName', { @@ -195,7 +195,7 @@ describe('DomainName', () => { }, ], MutualTlsAuthentication: { - TruststoreUri: 's3://exampleBucket/someca.pem', + TruststoreUri: 's3://example-bucket/someca.pem', }, }); }); @@ -203,7 +203,7 @@ describe('DomainName', () => { test('mTLS should allow versions to be set on the s3 bucket', () => { // GIVEN const stack = new Stack(); - const bucket = Bucket.fromBucketName(stack, 'testBucket', 'exampleBucket'); + const bucket = Bucket.fromBucketName(stack, 'testBucket', 'example-bucket'); // WHEN new DomainName(stack, 'DomainName', { @@ -226,7 +226,7 @@ describe('DomainName', () => { }, ], MutualTlsAuthentication: { - TruststoreUri: 's3://exampleBucket/someca.pem', + TruststoreUri: 's3://example-bucket/someca.pem', TruststoreVersion: 'version', }, });