Skip to content

Commit fb63c70

Browse files
serahisaacthoTikiTDO
authored andcommitted
feat(apigatewayv2): http api - mTLS support (aws#17284)
Resolves aws#12559 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent fe98237 commit fb63c70

File tree

4 files changed

+126
-0
lines changed

4 files changed

+126
-0
lines changed

Diff for: packages/@aws-cdk/aws-apigatewayv2/README.md

+24
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ Higher level constructs for Websocket APIs | ![Experimental](https://img.shields
3434
- [Cross Origin Resource Sharing (CORS)](#cross-origin-resource-sharing-cors)
3535
- [Publishing HTTP APIs](#publishing-http-apis)
3636
- [Custom Domain](#custom-domain)
37+
- [Mutual TLS](#mutual-tls-mtls)
3738
- [Managing access](#managing-access)
3839
- [Metrics](#metrics)
3940
- [VPC Link](#vpc-link)
@@ -254,6 +255,29 @@ declare const apiDemo: apigwv2.HttpApi;
254255
const demoDomainUrl = apiDemo.defaultStage?.domainUrl; // returns "https://example.com/demo"
255256
```
256257

258+
## Mutual TLS (mTLS)
259+
260+
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.
261+
262+
```ts
263+
import * as s3 from '@aws-cdk/aws-s3';
264+
const certArn = 'arn:aws:acm:us-east-1:111111111111:certificate';
265+
const domainName = 'example.com';
266+
const bucket = new s3.Bucket.fromBucketName(stack, 'TrustStoreBucket', ...);
267+
268+
new DomainName(stack, 'DomainName', {
269+
domainName,
270+
certificate: Certificate.fromCertificateArn(stack, 'cert', certArn),
271+
mtls: {
272+
bucket,
273+
key: 'someca.pem',
274+
version: 'version',
275+
},
276+
})
277+
```
278+
279+
Instructions for configuring your trust store can be found [here](https://aws.amazon.com/blogs/compute/introducing-mutual-tls-authentication-for-amazon-api-gateway/)
280+
257281
### Managing access
258282

259283
API Gateway supports multiple mechanisms for [controlling and managing access to your HTTP

Diff for: packages/@aws-cdk/aws-apigatewayv2/lib/common/domain-name.ts

+37
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ICertificate } from '@aws-cdk/aws-certificatemanager';
2+
import { IBucket } from '@aws-cdk/aws-s3';
23
import { IResource, Resource, Token } from '@aws-cdk/core';
34
import { Construct } from 'constructs';
45
import { CfnDomainName, CfnDomainNameProps } from '../apigatewayv2.generated';
@@ -59,6 +60,32 @@ export interface DomainNameProps {
5960
* The ACM certificate for this domain name
6061
*/
6162
readonly certificate: ICertificate;
63+
/**
64+
* The mutual TLS authentication configuration for a custom domain name.
65+
* @default - mTLS is not configured.
66+
*/
67+
readonly mtls?: MTLSConfig
68+
}
69+
70+
/**
71+
* The mTLS authentication configuration for a custom domain name.
72+
*/
73+
export interface MTLSConfig {
74+
/**
75+
* The bucket that the trust store is hosted in.
76+
*/
77+
readonly bucket: IBucket;
78+
/**
79+
* The key in S3 to look at for the trust store
80+
*/
81+
readonly key: string;
82+
83+
/**
84+
* The version of the S3 object that contains your truststore.
85+
* To specify a version, you must have versioning enabled for the S3 bucket.
86+
* @default - latest version
87+
*/
88+
readonly version?: string;
6289
}
6390

6491
/**
@@ -88,6 +115,7 @@ export class DomainName extends Resource implements IDomainName {
88115
throw new Error('empty string for domainName not allowed');
89116
}
90117

118+
const mtlsConfig = this.configureMTLS(props.mtls);
91119
const domainNameProps: CfnDomainNameProps = {
92120
domainName: props.domainName,
93121
domainNameConfigurations: [
@@ -96,10 +124,19 @@ export class DomainName extends Resource implements IDomainName {
96124
endpointType: 'REGIONAL',
97125
},
98126
],
127+
mutualTlsAuthentication: mtlsConfig,
99128
};
100129
const resource = new CfnDomainName(this, 'Resource', domainNameProps);
101130
this.name = resource.ref;
102131
this.regionalDomainName = Token.asString(resource.getAtt('RegionalDomainName'));
103132
this.regionalHostedZoneId = Token.asString(resource.getAtt('RegionalHostedZoneId'));
104133
}
134+
135+
private configureMTLS(mtlsConfig?: MTLSConfig): CfnDomainName.MutualTlsAuthenticationProperty | undefined {
136+
if (!mtlsConfig) return undefined;
137+
return {
138+
truststoreUri: mtlsConfig.bucket.s3UrlForObject(mtlsConfig.key),
139+
truststoreVersion: mtlsConfig.version,
140+
};
141+
}
105142
}

Diff for: packages/@aws-cdk/aws-apigatewayv2/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
"@aws-cdk/aws-cloudwatch": "0.0.0",
9090
"@aws-cdk/aws-ec2": "0.0.0",
9191
"@aws-cdk/aws-iam": "0.0.0",
92+
"@aws-cdk/aws-s3": "0.0.0",
9293
"@aws-cdk/core": "0.0.0",
9394
"constructs": "^3.3.69"
9495
},
@@ -97,6 +98,7 @@
9798
"@aws-cdk/aws-cloudwatch": "0.0.0",
9899
"@aws-cdk/aws-ec2": "0.0.0",
99100
"@aws-cdk/aws-iam": "0.0.0",
101+
"@aws-cdk/aws-s3": "0.0.0",
100102
"@aws-cdk/core": "0.0.0",
101103
"constructs": "^3.3.69"
102104
},

Diff for: packages/@aws-cdk/aws-apigatewayv2/test/http/domain-name.test.ts

+63
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Template } from '@aws-cdk/assertions';
22
import { Certificate } from '@aws-cdk/aws-certificatemanager';
3+
import { Bucket } from '@aws-cdk/aws-s3';
34
import { Stack } from '@aws-cdk/core';
45
import { DomainName, HttpApi } from '../../lib';
56

@@ -168,4 +169,66 @@ describe('DomainName', () => {
168169
expect(t).toThrow('defaultDomainMapping not supported with createDefaultStage disabled');
169170

170171
});
172+
173+
test('accepts a mutual TLS configuration', () => {
174+
// GIVEN
175+
const stack = new Stack();
176+
const bucket = Bucket.fromBucketName(stack, 'testBucket', 'example-bucket');
177+
178+
// WHEN
179+
new DomainName(stack, 'DomainName', {
180+
domainName,
181+
certificate: Certificate.fromCertificateArn(stack, 'cert', certArn),
182+
mtls: {
183+
bucket,
184+
key: 'someca.pem',
185+
},
186+
});
187+
188+
// THEN
189+
Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::DomainName', {
190+
DomainName: 'example.com',
191+
DomainNameConfigurations: [
192+
{
193+
CertificateArn: 'arn:aws:acm:us-east-1:111111111111:certificate',
194+
EndpointType: 'REGIONAL',
195+
},
196+
],
197+
MutualTlsAuthentication: {
198+
TruststoreUri: 's3://example-bucket/someca.pem',
199+
},
200+
});
201+
});
202+
203+
test('mTLS should allow versions to be set on the s3 bucket', () => {
204+
// GIVEN
205+
const stack = new Stack();
206+
const bucket = Bucket.fromBucketName(stack, 'testBucket', 'example-bucket');
207+
208+
// WHEN
209+
new DomainName(stack, 'DomainName', {
210+
domainName,
211+
certificate: Certificate.fromCertificateArn(stack, 'cert', certArn),
212+
mtls: {
213+
bucket,
214+
key: 'someca.pem',
215+
version: 'version',
216+
},
217+
});
218+
219+
// THEN
220+
Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::DomainName', {
221+
DomainName: 'example.com',
222+
DomainNameConfigurations: [
223+
{
224+
CertificateArn: 'arn:aws:acm:us-east-1:111111111111:certificate',
225+
EndpointType: 'REGIONAL',
226+
},
227+
],
228+
MutualTlsAuthentication: {
229+
TruststoreUri: 's3://example-bucket/someca.pem',
230+
TruststoreVersion: 'version',
231+
},
232+
});
233+
});
171234
});

0 commit comments

Comments
 (0)