Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(apigatewayv2): http api - mTLS support #17284

Merged
merged 9 commits into from
Nov 9, 2021
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions packages/@aws-cdk/aws-apigatewayv2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -254,6 +255,29 @@ declare const apiDemo: apigwv2.HttpApi;
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
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,
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
Expand Down
37 changes: 37 additions & 0 deletions packages/@aws-cdk/aws-apigatewayv2/lib/common/domain-name.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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;
}

/**
Expand Down Expand Up @@ -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: [
Expand All @@ -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,
};
}
}
2 changes: 2 additions & 0 deletions packages/@aws-cdk/aws-apigatewayv2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand All @@ -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"
},
Expand Down
63 changes: 63 additions & 0 deletions packages/@aws-cdk/aws-apigatewayv2/test/http/domain-name.test.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -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', 'example-bucket');

// 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://example-bucket/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', 'example-bucket');

// 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://example-bucket/someca.pem',
TruststoreVersion: 'version',
},
});
});
});