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(apigateway): mTLS support #10521

Merged
merged 9 commits into from
Oct 15, 2020
19 changes: 19 additions & 0 deletions packages/@aws-cdk/aws-apigateway/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ running on AWS Lambda, or any web application.
- [IAM-based authorizer](#iam-based-authorizer)
- [Lambda-based token authorizer](#lambda-based-token-authorizer)
- [Lambda-based request authorizer](#lambda-based-request-authorizer)
- [Mutual TLS](#mutal-tls-mtls)
- [Deployments](#deployments)
- [Deep dive: Invalidation of deployments](#deep-dive-invalidation-of-deployments)
- [Custom Domains](#custom-domains)
Expand Down Expand Up @@ -573,6 +574,24 @@ Authorizers can also be passed via the `defaultMethodOptions` property within th
explicitly overridden, the specified defaults will be applied across all `Method`s across the `RestApi` or across all `Resource`s,
depending on where the defaults were specified.

## 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
new apigw.DomainName(this, 'domain-name', {
domainName: 'example.com',
certificate: acm.Certificate.fromCertificateArn(this, 'cert' 'arn:aws:acm:us-east-1:1111111:certificate/11-3336f1-44483d-adc7-9cd375c5169d'),
mtls: {
bucket: new Bucket(this, 'bucket')),
key: 'truststore.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/).

## Deployments

By default, the `RestApi` construct will automatically create an API Gateway
Expand Down
41 changes: 40 additions & 1 deletion packages/@aws-cdk/aws-apigateway/lib/domain-name.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as acm 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 } from './apigateway.generated';
Expand Down Expand Up @@ -40,6 +41,12 @@ export interface DomainNameOptions {
* @default SecurityPolicy.TLS_1_0
*/
readonly securityPolicy?: SecurityPolicy

/**
* The mutual TLS authentication configuration for a custom domain name.
* @default - mTLS is not configured.
*/
readonly mtls?: MTLSConfig
}

export interface DomainNameProps extends DomainNameOptions {
Expand Down Expand Up @@ -76,6 +83,7 @@ export interface IDomainName extends IResource {
* @attribute DistributionHostedZoneId,RegionalHostedZoneId
*/
readonly domainNameAliasHostedZoneId: string;

}

export class DomainName extends Resource implements IDomainName {
Expand Down Expand Up @@ -107,12 +115,13 @@ export class DomainName extends Resource implements IDomainName {
throw new Error('domainName does not support uppercase letters. ' +
`got: '${props.domainName}'`);
}

const mtlsConfig = this.configureMTLS(props.mtls);
const resource = new CfnDomainName(this, 'Resource', {
domainName: props.domainName,
certificateArn: edge ? props.certificate.certificateArn : undefined,
regionalCertificateArn: edge ? undefined : props.certificate.certificateArn,
endpointConfiguration: { types: [endpointType] },
mutualTlsAuthentication: mtlsConfig,
securityPolicy: props.securityPolicy,
});

Expand Down Expand Up @@ -145,6 +154,14 @@ export class DomainName extends Resource implements IDomainName {
...options,
});
}

private configureMTLS(mtlsConfig?: MTLSConfig): CfnDomainName.MutualTlsAuthenticationProperty | undefined {
if (!mtlsConfig) return undefined;
return {
truststoreUri: mtlsConfig.bucket.s3UrlForObject(mtlsConfig.key),
truststoreVersion: mtlsConfig.version,
};
}
}

export interface DomainNameAttributes {
Expand All @@ -162,4 +179,26 @@ export interface DomainNameAttributes {
* Thje Route53 hosted zone ID to use in order to connect a record set to this domain through an alias.
*/
readonly domainNameAliasHostedZoneId: string;

}

/**
* 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;
}
44 changes: 44 additions & 0 deletions packages/@aws-cdk/aws-apigateway/test/test.domains.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ABSENT, expect, haveResource } from '@aws-cdk/assert';
import * as acm from '@aws-cdk/aws-certificatemanager';
import { Bucket } from '@aws-cdk/aws-s3';
import { Stack } from '@aws-cdk/core';
import { Test } from 'nodeunit';
import * as apigw from '../lib';
Expand Down Expand Up @@ -399,4 +400,47 @@ export = {
}));
test.done();
},

'accepts a mutual TLS configuration'(test: Test) {
const stack = new Stack();
const bucket = Bucket.fromBucketName(stack, 'testBucket', 'exampleBucket');
new apigw.DomainName(stack, 'another-domain', {
domainName: 'example.com',
mtls: {
bucket,
key: 'someca.pem',
},
certificate: acm.Certificate.fromCertificateArn(stack, 'cert', 'arn:aws:acm:us-east-1:1111111:certificate/11-3336f1-44483d-adc7-9cd375c5169d'),
});

expect(stack).to(haveResource('AWS::ApiGateway::DomainName', {
'DomainName': 'example.com',
'EndpointConfiguration': { 'Types': ['REGIONAL'] },
'RegionalCertificateArn': 'arn:aws:acm:us-east-1:1111111:certificate/11-3336f1-44483d-adc7-9cd375c5169d',
'MutualTlsAuthentication': { 'TruststoreUri': 's3://exampleBucket/someca.pem' },
}));
test.done();
},

'mTLS should allow versions to be set on the s3 bucket'(test: Test) {
const stack = new Stack();
const bucket = Bucket.fromBucketName(stack, 'testBucket', 'exampleBucket');
new apigw.DomainName(stack, 'another-domain', {
domainName: 'example.com',
certificate: acm.Certificate.fromCertificateArn(stack, 'cert2', 'arn:aws:acm:us-east-1:1111111:certificate/11-3336f1-44483d-adc7-9cd375c5169d'),
mtls: {
bucket,
key: 'someca.pem',
version: 'version',
},
});
expect(stack).to(haveResource('AWS::ApiGateway::DomainName', {
'DomainName': 'example.com',
'EndpointConfiguration': { 'Types': ['REGIONAL'] },
'RegionalCertificateArn': 'arn:aws:acm:us-east-1:1111111:certificate/11-3336f1-44483d-adc7-9cd375c5169d',
'MutualTlsAuthentication': { 'TruststoreUri': 's3://exampleBucket/someca.pem', 'TruststoreVersion': 'version' },
}));
test.done();
},

};