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 1 commit
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
12 changes: 9 additions & 3 deletions packages/@aws-cdk/aws-apigatewayv2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,10 +171,16 @@ custom domain to the `$default` stage of the API.
```ts
const certArn = 'arn:aws:acm:us-east-1:111111111111:certificate';
const domainName = 'example.com';

const dn = new DomainName(stack, 'DN', {
SmritiVashisth marked this conversation as resolved.
Show resolved Hide resolved
const domainNameConfigurations = new Array<DomainNameConfiguration>();
const dnConfig: DomainNameConfiguration = {
certificate: Certificate.fromCertificateArn(stack, 'cert', certArn),
endpointType: EndpointType.REGIONAL,
SmritiVashisth marked this conversation as resolved.
Show resolved Hide resolved
};
domainNameConfigurations.push(dnConfig);

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

const api = new HttpApi(stack, 'HttpProxyProdApi', {
Expand Down
136 changes: 129 additions & 7 deletions packages/@aws-cdk/aws-apigatewayv2/lib/common/domain-name.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,31 @@
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';


/**
* 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 a regional API and its custom domain name.
*/
REGIONAL = 'REGIONAL'
}
nija-at marked this conversation as resolved.
Show resolved Hide resolved

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

/**
* The ACM certificate for this domain name
* DomainNameConfigurations for a domain name.
* @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 mtls?: 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 edge-optimized
* endpoint for the domain name. For "EDGE" domain names, the certificate
* needs to be in the US East (N. Virginia) region.
*/
readonly certificate: ICertificate;

/**
* The user-friendly name of the certificate that will be used by the endpoint for this domain name.
* @default null
nija-at marked this conversation as resolved.
Show resolved Hide resolved
*/
readonly certificateName?: string;

/**
* The type of endpoint for this DomainName.
* @default REGIONAL
*/
readonly endpointType?: EndpointType;

/**
* The ARN of the public certificate issued by ACM to validate ownership of your
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* The ARN of the public certificate issued by ACM to validate ownership of your
* The public certificate issued by ACM to validate ownership of your

* custom domain. Only required when configuring mutual TLS and using an ACM
* imported or private CA certificate ARN as the RegionalCertificateArn.
Copy link

Choose a reason for hiding this comment

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

Let's fix RegionallCertificateArn usage here. You can work with our doc writer if needed for a more accurate description

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok

Copy link
Contributor

@nija-at nija-at Nov 4, 2021

Choose a reason for hiding this comment

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

Confusing. RegionalCertificateArn is not here AFAICT. This needs to be written in CDK terms and not just copy-pasted from CFN.

* @default null
SmritiVashisth marked this conversation as resolved.
Show resolved Hide resolved
*/
readonly ownershipVerificationCertificate?: ICertificate;

/**
* The Transport Layer Security (TLS) version + cipher suite for this domain name.
* @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-domainname.html
* @default SecurityPolicy.TLS_1_0
SmritiVashisth marked this conversation as resolved.
Show resolved Hide resolved
*/
readonly securityPolicy?: SecurityPolicy;
}

/**
* The mTLS authentication configuration for a custom domain name.
*/
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.
*/
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;
SmritiVashisth marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand All @@ -80,26 +172,56 @@ 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');
}

// domain name configuration null check
if (!props.domainNameConfigurations) {
throw new Error('empty domain name configurations are not allowed');
} else {
this.setDomainNameConfigurations(...props.domainNameConfigurations);
SmritiVashisth marked this conversation as resolved.
Show resolved Hide resolved
}

const mtlsConfig = this.configureMTLS(props.mtls);

const domainNameProps: CfnDomainNameProps = {
domainName: props.domainName,
domainNameConfigurations: [
{
certificateArn: props.certificate.certificateArn,
endpointType: 'REGIONAL',
},
],
domainNameConfigurations: this.domainNameConfigurations,
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 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,
};
}

}
7 changes: 6 additions & 1 deletion packages/@aws-cdk/aws-apigatewayv2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,16 @@
"@aws-cdk/cdk-integ-tools": "0.0.0",
"@aws-cdk/cfn2ts": "0.0.0",
"@aws-cdk/pkglint": "0.0.0",
"@types/jest": "^26.0.24"
"@types/jest": "^26.0.24",
"ts-node": "^10.4.0"
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this being added?

},
"dependencies": {
"@aws-cdk/aws-certificatemanager": "0.0.0",
"@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/aws-s3-assets": "0.0.0",
SmritiVashisth marked this conversation as resolved.
Show resolved Hide resolved
"@aws-cdk/core": "0.0.0",
"constructs": "^3.3.69"
},
Expand All @@ -98,6 +101,8 @@
"@aws-cdk/aws-ec2": "0.0.0",
"@aws-cdk/aws-iam": "0.0.0",
"@aws-cdk/core": "0.0.0",
"@aws-cdk/aws-s3": "0.0.0",
"@aws-cdk/aws-s3-assets": "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
Loading