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 - domain endpoint type, security policy and mTLS #17219

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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
36 changes: 36 additions & 0 deletions packages/@aws-cdk/aws-apigatewayv2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,10 +177,26 @@ Note that, `HttpApi` will always creates a `$default` stage, unless the `createD
### Custom Domain

Custom domain names are simpler and more intuitive URLs that you can provide to your API users. Custom domain name are associated to API stages.
Learn more at [Setting up custom domain name for an HTTP API](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-custom-domain-names.html)
and [Setting up custom domain name for a Websocket API](https://docs.aws.amazon.com/apigateway/latest/developerguide/websocket-api-custom-domain-names.html)
SmritiVashisth marked this conversation as resolved.
Show resolved Hide resolved

The code snippet below creates a custom domain and configures a default domain mapping for your API that maps the
custom domain to the `$default` stage of the API.

Domain Names in apigatewayv2 have multiple nested configurations.

A DomainNameConfiguration specifies the certificate, endpoint and security policy for a domain name. Each configuration must have a unique endpoint type.
You need only one DomainNameConfiguration, unless you're migrating from one endpoint type to another. To migrate to a new endpoint type,
you add a new domain name configuration and then configure DNS records to route traffic to the new endpoint. After that, you can remove the previous DomainNameConfiguration.
Learn more at [Migrating a custom domain name](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-regional-api-custom-domain-migrate.html)
SmritiVashisth marked this conversation as resolved.
Show resolved Hide resolved

MutualTLS configures the TLS authentication between client and server. Clients must present a trusted certificate to access the API associated with a domain.
SmritiVashisth marked this conversation as resolved.
Show resolved Hide resolved
MutualTLS configures two-way authentication between the client and the server. With mutual TLS, clients must present X.509 certificates to verify their identity
to access your APIs.
You can use mutual TLS along with other authorization and authentication operations that API Gateway supports. API Gateway forwards the certificates
that clients provide to Lambda authorizers and to backend integrations.
Learn more at [Configuring MutualTLS for an API](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-mutual-tls.html).

```ts
import * as acm from '@aws-cdk/aws-certificatemanager';
import { LambdaProxyIntegration } from '@aws-cdk/aws-apigatewayv2-integrations';
Expand All @@ -204,6 +220,26 @@ const api = new apigwv2.HttpApi(this, 'HttpProxyProdApi', {
});
```

To set up Mutual TLS for a custom domain

```ts
const dn = new DomainName(stack, 'DomainName', {
domainName: 'example.com',
SmritiVashisth marked this conversation as resolved.
Show resolved Hide resolved
domainNameConfigurations: [
{
certificate: Certificate.fromCertificateArn(stack, 'cert', 'arn:aws:acm:us-east-1:111111111111:certificate'),
endpointType: EndpointType.REGIONAL,
securityPolicy: 'TLS_1_2',
},
],
mutualTlsConfiguration: {
bucket: new Bucket(this, 'bucket'),
key: 'someca.pem',
version: 'version',
},
});
```

To associate a specific `Stage` to a custom domain mapping -

```ts
Expand Down
73 changes: 71 additions & 2 deletions packages/@aws-cdk/aws-apigatewayv2/lib/common/domain-name.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,32 @@ import { IResource, Resource, Token } from '@aws-cdk/core';
import { Construct } from 'constructs';
import { CfnDomainName, CfnDomainNameProps } from '../apigatewayv2.generated';


/**
* The minimum version of the SSL protocol that you want API Gateway to use for HTTPS connections.
*/
export enum SecurityPolicy {
/** Cipher suite TLS 1.0 */
TLS_1_0 = 'TLS_1_0',

/** Cipher suite TLS 1.2 */
TLS_1_2 = 'TLS_1_2',
}

/**
* Endpoint type for a domain name.
*/
export enum EndpointType {
SmritiVashisth marked this conversation as resolved.
Show resolved Hide resolved
/**
* For an edge-optimized custom domain name.
*/
EDGE = 'EDGE',
/**
* For a regional custom domain name.
*/
REGIONAL = 'REGIONAL'
}

/**
* Represents an APIGatewayV2 DomainName
* @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigatewayv2-domainname.html
Expand Down Expand Up @@ -56,10 +82,37 @@ export interface DomainNameProps {
* The custom domain name
*/
readonly domainName: string;

/**
* The ACM certificate for this domain name
* DomainNameConfigurations for a domain name, includes properties associated with a DomainName - certificate, endpoint type, security policy.
* Each configuration must have a unique Endpoint type. For general use, only a single DomainNameConfiguration is needed.
* When migrating domain names from one endpoint to another, more than one DomainNameConfiguration is added. This second configuration
* creates the set-up required to migrate the domain name to this endpoint. When you set up a DNS record to point the domain name to the
* new hostname, the traffic bound to the custom domain name gets routed to the new host. After this, the first DomainNameConfiguration
* can be removed to have the custom domain name associated only with the migrated endpoint.
* @link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigatewayv2-domainname.html#cfn-apigatewayv2-domainname-domainnameconfigurations
*/
readonly domainNameConfigurations: DomainNameConfiguration[];
SmritiVashisth marked this conversation as resolved.
Show resolved Hide resolved

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

/**
* Specifies the configuration for a an API's domain name.
*/
export interface DomainNameConfiguration {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since adding multiple domain name configuration is only during migration, and is not the dominant use case, I suggest changing the API like this -

const domain = new DomainName(stack, 'DomainName', {
   domainName: 'example.com',
   certificate: '...',
   endpointType: '...'
});
domain.addConfiguration({
  certificate: '...',
  endpointType: '...',
  ...
});

This keeps the API clean for the majority of users, while enabling the migration use case.
More importantly, it also avoids an API breaking change.

/**
* The reference to an AWS-managed certificate for use by the domain name.
* For "EDGE" domain names, the certificate needs to be in the US East (N. Virginia) region.
* For "REGIONAL" domains, certificate is in the same region as the domain.
* Certificate can be both ACM issued or imported.
*/
readonly certificate: ICertificate;

/**
* The mutual TLS authentication configuration for a custom domain name.
* @default - mTLS is not configured.
Expand All @@ -75,6 +128,7 @@ export interface MTLSConfig {
* The bucket that the trust store is hosted in.
*/
readonly bucket: IBucket;
SmritiVashisth marked this conversation as resolved.
Show resolved Hide resolved

/**
* The key in S3 to look at for the trust store
*/
Expand Down Expand Up @@ -107,10 +161,12 @@ export class DomainName extends Resource implements IDomainName {
public readonly name: string;
public readonly regionalDomainName: string;
public readonly regionalHostedZoneId: string;
private readonly domainNameConfigurations = new Array<CfnDomainName.DomainNameConfigurationProperty>();

constructor(scope: Construct, id: string, props: DomainNameProps) {
super(scope, id);

// domain name null check
if (props.domainName === '') {
throw new Error('empty string for domainName not allowed');
}
Expand All @@ -132,11 +188,24 @@ export class DomainName extends Resource implements IDomainName {
this.regionalHostedZoneId = Token.asString(resource.getAtt('RegionalHostedZoneId'));
}

private setDomainNameConfigurations(...domainNameConfigurations: DomainNameConfiguration[]) {
domainNameConfigurations.forEach( (config) => {
SmritiVashisth marked this conversation as resolved.
Show resolved Hide resolved
const ownershipCertArn = (config.ownershipVerificationCertificate) ? config.ownershipVerificationCertificate.certificateArn : undefined;
const domainNameConfig: CfnDomainName.DomainNameConfigurationProperty = {
certificateArn: config.certificate.certificateArn,
certificateName: config.certificateName,
endpointType: config.endpointType,
ownershipVerificationCertificateArn: ownershipCertArn,
securityPolicy: config.securityPolicy?.toString(),
};
this.domainNameConfigurations.push(domainNameConfig);
});
}

private configureMTLS(mtlsConfig?: MTLSConfig): CfnDomainName.MutualTlsAuthenticationProperty | undefined {
if (!mtlsConfig) return undefined;
return {
truststoreUri: mtlsConfig.bucket.s3UrlForObject(mtlsConfig.key),
truststoreVersion: mtlsConfig.version,
};
}
}
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-apigatewayv2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
"@aws-cdk/aws-iam": "0.0.0",
"@aws-cdk/aws-s3": "0.0.0",
"@aws-cdk/core": "0.0.0",
"@aws-cdk/aws-s3": "0.0.0",
"constructs": "^3.3.69"
},
"engines": {
Expand Down
57 changes: 50 additions & 7 deletions packages/@aws-cdk/aws-apigatewayv2/test/common/api-mapping.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Template } from '@aws-cdk/assertions';
import { Certificate } from '@aws-cdk/aws-certificatemanager';
import { Stack } from '@aws-cdk/core';
import { DomainName, HttpApi, ApiMapping, WebSocketApi } from '../../lib';
import { DomainName, HttpApi, ApiMapping, WebSocketApi, DomainNameConfiguration, EndpointType } from '../../lib';

const domainName = 'example.com';
const certArn = 'arn:aws:acm:us-east-1:111111111111:certificate';
Expand All @@ -12,9 +12,16 @@ describe('ApiMapping', () => {
const stack = new Stack();
const api = new HttpApi(stack, 'Api');

const domainNameConfigurations = new Array<DomainNameConfiguration>();
const dnConfig: DomainNameConfiguration = {
certificate: Certificate.fromCertificateArn(stack, 'cert', certArn),
endpointType: EndpointType.REGIONAL,
};
domainNameConfigurations.push(dnConfig);

const dn = new DomainName(stack, 'DomainName', {
domainName,
certificate: Certificate.fromCertificateArn(stack, 'cert', certArn),
domainNameConfigurations,
});

new ApiMapping(stack, 'Mapping', {
Expand All @@ -41,9 +48,16 @@ describe('ApiMapping', () => {
stageName: 'beta',
});

const domainNameConfigurations = new Array<DomainNameConfiguration>();
const dnConfig: DomainNameConfiguration = {
certificate: Certificate.fromCertificateArn(stack, 'cert', certArn),
endpointType: EndpointType.REGIONAL,
};
domainNameConfigurations.push(dnConfig);

const dn = new DomainName(stack, 'DomainName', {
domainName,
certificate: Certificate.fromCertificateArn(stack, 'cert', certArn),
domainNameConfigurations,
});

new ApiMapping(stack, 'Mapping', {
Expand All @@ -67,9 +81,16 @@ describe('ApiMapping', () => {
const stack = new Stack();
const api = new HttpApi(stack, 'Api');

const domainNameConfigurations = new Array<DomainNameConfiguration>();
const dnConfig: DomainNameConfiguration = {
certificate: Certificate.fromCertificateArn(stack, 'cert', certArn),
endpointType: EndpointType.REGIONAL,
};
domainNameConfigurations.push(dnConfig);

const dn = new DomainName(stack, 'DomainName', {
domainName,
certificate: Certificate.fromCertificateArn(stack, 'cert', certArn),
domainNameConfigurations,
});

expect(() => {
Expand All @@ -86,9 +107,16 @@ describe('ApiMapping', () => {
const stack = new Stack();
const api = new HttpApi(stack, 'Api');

const domainNameConfigurations = new Array<DomainNameConfiguration>();
const dnConfig: DomainNameConfiguration = {
certificate: Certificate.fromCertificateArn(stack, 'cert', certArn),
endpointType: EndpointType.REGIONAL,
};
domainNameConfigurations.push(dnConfig);

const dn = new DomainName(stack, 'DomainName', {
domainName,
certificate: Certificate.fromCertificateArn(stack, 'cert', certArn),
domainNameConfigurations,
});

const mapping = new ApiMapping(stack, 'Mapping', {
Expand All @@ -109,9 +137,17 @@ describe('ApiMapping', () => {
const api = new HttpApi(stack, 'Api', {
createDefaultStage: false,
});

const domainNameConfigurations = new Array<DomainNameConfiguration>();
const dnConfig: DomainNameConfiguration = {
certificate: Certificate.fromCertificateArn(stack, 'cert', certArn),
endpointType: EndpointType.REGIONAL,
};
domainNameConfigurations.push(dnConfig);

const dn = new DomainName(stack, 'DomainName', {
domainName,
certificate: Certificate.fromCertificateArn(stack, 'cert', certArn),
domainNameConfigurations,
});

// WHEN
Expand All @@ -127,9 +163,16 @@ describe('ApiMapping', () => {
// GIVEN
const stack = new Stack();
const api = new WebSocketApi(stack, 'api');
const domainNameConfigurations = new Array<DomainNameConfiguration>();
const dnConfig: DomainNameConfiguration = {
certificate: Certificate.fromCertificateArn(stack, 'cert', certArn),
endpointType: EndpointType.REGIONAL,
};
domainNameConfigurations.push(dnConfig);

const dn = new DomainName(stack, 'DomainName', {
domainName,
certificate: Certificate.fromCertificateArn(stack, 'cert', certArn),
domainNameConfigurations,
});

// WHEN
Expand Down
6 changes: 5 additions & 1 deletion packages/@aws-cdk/aws-apigatewayv2/test/http/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,11 @@ describe('HttpApi', () => {
const stack = new Stack();
const dn = new DomainName(stack, 'DN', {
domainName: 'example.com',
certificate: Certificate.fromCertificateArn(stack, 'cert', 'arn:aws:acm:us-east-1:111111111111:certificate'),
domainNameConfigurations: [
{
certificate: Certificate.fromCertificateArn(stack, 'cert', 'arn:aws:acm:us-east-1:111111111111:certificate'),
},
],
});

const api = new HttpApi(stack, 'Api', {
Expand Down
Loading