diff --git a/packages/aws-cdk-lib/aws-cloudfront/README.md b/packages/aws-cdk-lib/aws-cloudfront/README.md index f6ad4cc4d1670..cec3592eb5908 100644 --- a/packages/aws-cdk-lib/aws-cloudfront/README.md +++ b/packages/aws-cdk-lib/aws-cloudfront/README.md @@ -67,6 +67,296 @@ new cloudfront.Distribution(this, 'myDist', { defaultBehavior: { origin: new origins.HttpOrigin('www.example.com') }, }); ``` +### CloudFront SaaS Manager resources + +#### Multi-tenant distribution and tenant providing ACM certificates +You can use Cloudfront to build multi-tenant distributions to house applications. + +To create a multi-tenant distribution w/parameters, create a Distribution construct, and then update DistributionConfig in the CfnDistribution to use connectionMode: "tenant-only": +```ts +// Create the simple Origin +const myBucket = new s3.Bucket(this, 'myBucket'); +const s3Origin = origins.S3BucketOrigin.withOriginAccessControl(myBucket, { + originAccessLevels: [cloudfront.AccessLevel.READ, cloudfront.AccessLevel.LIST], +}); + +// Create the Distribution construct +const myMultiTenantDistribution = new cloudfront.Distribution(this, 'distribution', { + defaultBehavior: { + origin: s3Origin, + }, + defaultRootObject: 'index.html', // recommended to specify +}); + +// Access the underlying L1 CfnDistribution to configure SaaS Manager properties which are not yet available in the L2 Distribution construct +const cfnDistribution = myMultiTenantDistribution.node.defaultChild as cloudfront.CfnDistribution; + +const defaultCacheBehavior: cloudfront.DefaultCacheBehaviorProperty = { + targetOriginId: myBucket.bucketArn, + viewerProtocolPolicy: 'allow-all', + compress: false, + allowedMethods: ['GET', 'HEAD'], + cachePolicyId: cloudfront.CachePolicy.CACHING_OPTIMIZED.cachePolicyId +}; +// Create the updated distributionConfig +const distributionConfig: cloudfront.DistributionConfigProperty = { + defaultCacheBehavior: defaultCacheBehavior, + enabled: true, + // the properties below are optional + connectionMode: 'tenant-only', + origins: [ + { + id: myBucket.bucketArn, + domainName: myBucket.bucketDomainName, + s3OriginConfig: {}, + originPath: "/{{tenantName}}" + }, + ], + tenantConfig: { + parameterDefinitions: [ + { + definition: { + stringSchema: { + required: false, + // the properties below are optional + comment: 'tenantName', + defaultValue: 'root', + }, + }, + name: 'tenantName', + }, + ], + }, +}; + +// Override the distribution configuration to enable multi-tenancy. +cfnDistribution.distributionConfig = distributionConfig; +``` + +Create a distribution tenant using an existing ACM certificate +```ts +const cfnDistributionTenant = new cloudfront.CfnDistributionTenant(this, 'distribution-tenant', { + distributionId: myMultiTenantDistribution.distributionId, + domains: ['my-tenant.my.domain.com'], + name: 'my-tenant', + enabled: true, + parameters: [ // Override the default 'tenantName' parameter (root) defined in the multi-tenant distribution. + { + name: 'tenantName', + value: 'app', + }, + ], + customizations: { + certificate: { + arn: 'REPLACE_WITH_ARN', // Certificate must be in us-east-1 region and cover 'my-tenant.my.domain.com' + }, + }, +}); +``` + +#### Multi-tenant distribution and tenant with CloudFront-hosted certificate +A distribution tenant with CloudFront-hosted domain validation is useful if you don't currently have traffic to the domain. + +Start by creating a parent multi-tenant distribution +```ts +// Create the simple Origin +const myBucket = new s3.Bucket(this, 'myBucket'); +const s3Origin = origins.S3BucketOrigin.withOriginAccessControl(myBucket, { + originAccessLevels: [cloudfront.AccessLevel.READ, cloudfront.AccessLevel.LIST], +}); + +// Create the Distribution construct +const myMultiTenantDistribution = new cloudfront.Distribution(this, 'cf-hosted-distribution', { + defaultBehavior: { + origin: s3Origin, + }, + defaultRootObject: 'index.html', // recommended to specify +}); + +// Access the underlying L1 CfnDistribution to configure SaaS Manager properties which are not yet available in the L2 Distribution construct +const cfnDistribution = myMultiTenantDistribution.node.defaultChild as cloudfront.CfnDistribution; + +const defaultCacheBehavior: cloudfront.DefaultCacheBehaviorProperty = { + targetOriginId: myBucket.bucketArn, + viewerProtocolPolicy: 'allow-all', + compress: false, + allowedMethods: ['GET', 'HEAD'], + cachePolicyId: cloudfront.CachePolicy.CACHING_OPTIMIZED.cachePolicyId +}; +// Create the updated distributionConfig +const distributionConfig: cloudfront.DistributionConfigProperty = { + defaultCacheBehavior: defaultCacheBehavior, + enabled: true, + // the properties below are optional + connectionMode: 'tenant-only', + origins: [ + { + id: myBucket.bucketArn, + domainName: myBucket.bucketDomainName, + s3OriginConfig: {}, + originPath: "/{{tenantName}}" + }, + ], + tenantConfig: { + parameterDefinitions: [ + { + definition: { + stringSchema: { + required: false, + // the properties below are optional + comment: 'tenantName', + defaultValue: 'root', + }, + }, + name: 'tenantName', + }, + ], + }, +}; + +// Override the distribution configuration to enable multi-tenancy. +cfnDistribution.distributionConfig = distributionConfig; +``` + +Create a connection group and a cname record in an existing hosted zone to validate domain ownership +```ts +const connectionGroup = new cloudfront.CfnConnectionGroup(this, 'cf-hosted-connection-group', { + enabled: true, + ipv6Enabled: true, + name: 'my-connection-group', +}); + +// Import the existing hosted zone info, replacing with your hostedZoneId and zoneName +const hostedZoneId = 'YOUR_HOSTED_ZONE_ID'; +const zoneName = 'my.domain.com'; +const hostedZone = HostedZone.fromHostedZoneAttributes(this, 'hosted-zone', { + hostedZoneId, + zoneName, +}); + +const record = new CnameRecord(this, 'cname-record', { + domainName: connectionGroup.attrRoutingEndpoint, + zone: hostedZone, + recordName: 'cf-hosted-tenant.my.domain.com', +}); +``` + +Create the cloudfront-hosted tenant, passing in the previously created connection group +```ts +const cloudfrontHostedTenant = new cloudfront.CfnDistributionTenant(this, 'cf-hosted-tenant', { + distributionId: myMultiTenantDistribution.distributionId, + name: 'cf-hosted-tenant', + domains: ['cf-hosted-tenant.my.domain.com'], + connectionGroupId: connectionGroup.attrId, + enabled: true, + managedCertificateRequest: { + validationTokenHost: 'cloudfront' + }, +}); +``` + +#### Multi-tenant distribution and tenant with self-hosted certificate +A tenant with self-hosted domain validation is useful if you already have traffic to the domain and can't tolerate downtime during migration to multi-tenant architecture. + +The tenant will be created, and the managed certificate will be awaiting validation of domain ownership. You can then validate domain ownership via http redirect or token file upload. [More details here](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/managed-cloudfront-certificates.html#complete-domain-ownership) + +Traffic won't be migrated until you update your hosted zone to point the tenant domain to the CloudFront RoutingEndpoint. + +Start by creating a parent multi-tenant distribution +```ts +// Create the simple Origin +const myBucket = new s3.Bucket(this, 'myBucket'); +const s3Origin = origins.S3BucketOrigin.withOriginAccessControl(myBucket, { + originAccessLevels: [cloudfront.AccessLevel.READ, cloudfront.AccessLevel.LIST], +}); + +// Create the Distribution construct +const myMultiTenantDistribution = new cloudfront.Distribution(this, 'cf-hosted-distribution', { + defaultBehavior: { + origin: s3Origin, + }, + defaultRootObject: 'index.html', // recommended to specify +}); + +// Access the underlying L1 CfnDistribution to configure SaaS Manager properties which are not yet available in the L2 Distribution construct +const cfnDistribution = myMultiTenantDistribution.node.defaultChild as cloudfront.CfnDistribution; + +const defaultCacheBehavior: cloudfront.DefaultCacheBehaviorProperty = { + targetOriginId: myBucket.bucketArn, + viewerProtocolPolicy: 'allow-all', + compress: false, + allowedMethods: ['GET', 'HEAD'], + cachePolicyId: cloudfront.CachePolicy.CACHING_OPTIMIZED.cachePolicyId +}; +// Create the updated distributionConfig +const distributionConfig: cloudfront.DistributionConfigProperty = { + defaultCacheBehavior: defaultCacheBehavior, + enabled: true, + // the properties below are optional + connectionMode: 'tenant-only', + origins: [ + { + id: myBucket.bucketArn, + domainName: myBucket.bucketDomainName, + s3OriginConfig: {}, + originPath: "/{{tenantName}}" + }, + ], + tenantConfig: { + parameterDefinitions: [ + { + definition: { + stringSchema: { + required: false, + // the properties below are optional + comment: 'tenantName', + defaultValue: 'root', + }, + }, + name: 'tenantName', + }, + ], + }, +}; + +// Override the distribution configuration to enable multi-tenancy. +cfnDistribution.distributionConfig = distributionConfig; +``` + +Create a connection group so we have access to the RoutingEndpoint associated with the tenant we are about to create +```ts +const connectionGroup = new cloudfront.CfnConnectionGroup(this, 'self-hosted-connection-group', { + enabled: true, + ipv6Enabled: true, + name: 'self-hosted-connection-group', +}); +``` + +Create a distribution tenant with a self-hosted domain. +```ts +const selfHostedTenant = new cloudfront.CfnDistributionTenant(this, 'self-hosted-tenant', { + distributionId: myMultiTenantDistribution.distributionId, + connectionGroupId: connectionGroup.attrId, + name: 'self-hosted-tenant', + domains: ['self-hosted-tenant.my.domain.com'], + enabled: true, + managedCertificateRequest: { + primaryDomainName: 'self-hosted-tenant.my.domain.com', + validationTokenHost: 'self-hosted', + }, +}); + +// Export the RoutingEndpoint, skip this step if you'd prefer to fetch it from the CloudFront console or via Cloudfront.ListConnectionGroups API +new cdk.CfnOutput(this, 'RoutingEndpoint', { + value: connectionGroup.attrRoutingEndpoint, + description: 'CloudFront Routing Endpoint to be added to my hosted zone CNAME records', +}); +``` +After this self-hosted tenant is created, [follow the steps here](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/managed-cloudfront-certificates.html#complete-domain-ownership) to complete domain setup. + +Validate domain ownership using the section "I have existing traffic" + +Then, when you are ready to accept traffic, follow the steps [here](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/managed-cloudfront-certificates.html#point-domains-to-cloudfront) using the RoutingEndpoint from above ### VPC origins