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 5 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
49 changes: 46 additions & 3 deletions packages/@aws-cdk/aws-apigatewayv2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,17 +164,40 @@ 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
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
securityPolicy: 'TLS_1_2',
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,
});
SmritiVashisth marked this conversation as resolved.
Show resolved Hide resolved

const api = new HttpApi(stack, 'HttpProxyProdApi', {
Expand All @@ -187,6 +210,26 @@ const api = new HttpApi(stack, '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
151 changes: 144 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,35 @@
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 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 @@ -55,10 +82,90 @@ 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 user-friendly name of the certificate that will be used by the endpoint for this domain name.
* This property is optional and is helpful if you have too many certificates and it is easier to remember
* certificates by some name rather that the domain they are valid for.
* Not specifying this property has no impact on the domain name functionality.
* @default null
nija-at marked this conversation as resolved.
Show resolved Hide resolved
SmritiVashisth 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 public certificate issued by ACM to validate ownership of your custom domain.
* Optional property, only required when you configure mutual TLS while using an ACM imported or private CA
* certificate as 'certificate'. In such a situation, ownershipVerificationCertificate acts as the ACM issued
* certificate that verifies the ownership of the custom domain with which 'certificate' is associated.
* Since this property is not 'required', the default is null when this property is not set.
* @default null
SmritiVashisth marked this conversation as resolved.
Show resolved Hide resolved
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_2
*/
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 +187,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.mutualTlsConfiguration);

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
Loading