The Certificate Stack is the Phase 1.5 infrastructure layer of the EveryoneCook project. It manages AWS Certificate Manager (ACM) certificates for CloudFront CDN and API Gateway, providing SSL/TLS encryption for all HTTPS traffic.
Deployment Order: This stack MUST be deployed after DNS Stack and before Core Stack and Backend Stack.
Critical Region Requirement: This stack MUST be deployed in us-east-1 region because CloudFront is a global service that can only access ACM certificates from us-east-1.
cdn.everyonecook.cloud or cdn-dev.everyonecook.cloud)*.everyonecook.cloud)┌─────────────────────────────────────────────────────────────────┐
│ Route 53 Hosted Zone │
│ everyonecook.cloud │
│ (from DNS Stack) │
└───────────────────┬─────────────────────────────────────────────┘
│ DNS Validation
▼
┌─────────────────────────────────────────────────────────────────┐
│ AWS Certificate Manager (us-east-1) │
│ │
│ Certificate 1: CloudFront Certificate │
│ ├─ Domain: cdn.everyonecook.cloud (or cdn-dev) │
│ ├─ Validation: DNS (Route 53) │
│ ├─ Status: Issued (5-10 minutes) │
│ └─ Export: CloudFrontCertificateArn │
│ │
│ Certificate 2: API Gateway Wildcard Certificate │
│ ├─ Domain: *.everyonecook.cloud │
│ ├─ SAN: everyonecook.cloud │
│ ├─ Covers: api.everyonecook.cloud, api-dev, api-staging │
│ ├─ Validation: DNS (Route 53) │
│ ├─ Status: Issued (5-10 minutes) │
│ └─ Export: ApiGatewayCertificateArn │
│ │
│ Cost Optimization: │
│ CloudFront WAF removed (-$9/month) │
│ Shield Standard (free, auto-enabled) │
└─────────────────────────────────────────────────────────────────┘
│
│ Certificate ARN Exports
▼
┌───────────────────────┬
▼ ▼
Core Stack Backend Stack
(CloudFront) (API Gateway)
infrastructure/lib/stacks/
└── certificate-stack.ts # Certificate Stack implementation
File: infrastructure/lib/stacks/certificate-stack.ts
import * as cdk from 'aws-cdk-lib';
import * as acm from 'aws-cdk-lib/aws-certificatemanager';
import * as route53 from 'aws-cdk-lib/aws-route53';
import { Construct } from 'constructs';
import { BaseStack, BaseStackProps } from '../base-stack';
/**
* Certificate Stack for CloudFront and API Gateway
*
* This stack creates ACM certificates for CloudFront and API Gateway.
*
* IMPORTANT REGION REQUIREMENTS:
* - CloudFront certificate: MUST be in us-east-1 (CloudFront requirement)
* - API Gateway certificate: Should be in same region as API Gateway (ap-southeast-1)
*
* This stack is deployed in us-east-1 to handle CloudFront's cross-region requirements.
* For API Gateway, we use a wildcard certificate that covers api.everyonecook.cloud.
*
* Responsibilities:
* - Create ACM certificate for CloudFront in us-east-1
* - Create ACM wildcard certificate for API Gateway in us-east-1 (works globally)
* - Validate certificates via Route 53 DNS
* - Export certificate ARNs for Core Stack and Backend Stack to use
*
* COST OPTIMIZATION NOTE:
* - CloudFront WAF removed to save $9/month ($108/year)
* - CloudFront still protected by Shield Standard (free, auto-enabled)
* - API Gateway has full WAF protection (BackendStack)
*/
export class CertificateStack extends BaseStack {
public readonly cloudFrontCertificate: acm.ICertificate;
public readonly apiGatewayCertificate: acm.ICertificate;
constructor(scope: Construct, id: string, props: BaseStackProps) {
super(scope, id, props);
// Add stack-specific tags
cdk.Tags.of(this).add('StackType', 'Certificate');
cdk.Tags.of(this).add('Layer', 'Infrastructure');
cdk.Tags.of(this).add('CostCenter', `Certificate-${this.config.environment}`);
// Import Route 53 Hosted Zone from DNS Stack
// Note: Cannot use Fn.importValue or SSM Parameter for cross-region references
// Hosted Zone ID is stable and doesn't change, so we hardcode it
// This value comes from DNS Stack output: Z018823421GWCSYG5UMHV
const hostedZoneId = 'Z018823421GWCSYG5UMHV';
const hostedZone = route53.HostedZone.fromHostedZoneAttributes(this, 'HostedZone', {
hostedZoneId: hostedZoneId,
zoneName: 'everyonecook.cloud',
});
// Create ACM certificate for CloudFront
// This certificate MUST be in us-east-1 for CloudFront to use it
this.cloudFrontCertificate = this.createCloudFrontCertificate(hostedZone);
// Create ACM wildcard certificate for API Gateway
// Wildcard *.everyonecook.cloud covers api.everyonecook.cloud
// This certificate in us-east-1 can be used by API Gateway in any region
this.apiGatewayCertificate = this.createApiGatewayCertificate(hostedZone);
// COST OPTIMIZATION: CloudFront WAF removed
// CloudFront is protected by Shield Standard (free, auto-enabled)
// API Gateway has full WAF protection in BackendStack
// Savings: $9/month ($108/year)
// Export certificate ARNs
this.exportOutputs();
}
/**
* Create ACM certificate for CloudFront CDN
*
* CRITICAL: This stack MUST be deployed in us-east-1 region.
* CloudFront is a global service but its control plane is in us-east-1,
* so it can only access certificates from us-east-1.
*
* DNS validation is automatic via Route 53.
* Validation typically takes 5-10 minutes.
*
* @param hostedZone - Route 53 Hosted Zone for DNS validation
* @returns ACM Certificate for CloudFront
*/
private createCloudFrontCertificate(hostedZone: route53.IHostedZone): acm.Certificate {
const certificate = new acm.Certificate(this, 'CloudFrontCertificate', {
domainName: this.config.domains.cdn,
validation: acm.CertificateValidation.fromDns(hostedZone),
certificateName: `EveryoneCook-CloudFront-${this.config.environment}`,
});
// Add tags
cdk.Tags.of(certificate).add('Component', 'CloudFront');
cdk.Tags.of(certificate).add('Purpose', 'CDN-SSL');
return certificate;
}
/**
* Create ACM wildcard certificate for API Gateway
*
* Creates a wildcard certificate (*.everyonecook.cloud) that covers:
* - api.everyonecook.cloud (API Gateway)
* - api-dev.everyonecook.cloud (API Gateway dev)
* - api-staging.everyonecook.cloud (API Gateway staging)
*
* This certificate is created in us-east-1 but can be used by API Gateway
* in any region via cross-region certificate reference.
*
* DNS validation is automatic via Route 53.
* Validation typically takes 5-10 minutes.
*
* @param hostedZone - Route 53 Hosted Zone for DNS validation
* @returns ACM Certificate for API Gateway
*/
private createApiGatewayCertificate(hostedZone: route53.IHostedZone): acm.Certificate {
const certificate = new acm.Certificate(this, 'ApiGatewayCertificate', {
domainName: '*.everyonecook.cloud', // Wildcard covers api.everyonecook.cloud
subjectAlternativeNames: ['everyonecook.cloud'], // Also covers root domain
validation: acm.CertificateValidation.fromDns(hostedZone),
certificateName: `EveryoneCook-API-${this.config.environment}`,
});
// Add tags
cdk.Tags.of(certificate).add('Component', 'APIGateway');
cdk.Tags.of(certificate).add('Purpose', 'API-SSL');
return certificate;
}
/**
* Export stack outputs for cross-stack references
*
* Exports:
* - CloudFrontCertificateArn: ACM Certificate ARN for CloudFront (us-east-1)
* - ApiGatewayCertificateArn: ACM Certificate ARN for API Gateway (us-east-1)
*
* REMOVED: CloudFrontWebAclArn (cost optimization)
*/
private exportOutputs(): void {
// Export CloudFront certificate ARN for Core Stack
new cdk.CfnOutput(this, 'CloudFrontCertificateArn', {
value: this.cloudFrontCertificate.certificateArn,
exportName: this.exportName('CloudFrontCertificateArn'),
description: 'ACM Certificate ARN for CloudFront (us-east-1)',
});
// Export CloudFront certificate domain for verification
new cdk.CfnOutput(this, 'CloudFrontCertificateDomain', {
value: this.config.domains.cdn,
description: 'Domain name for CloudFront certificate',
});
// Export API Gateway certificate ARN for Backend Stack
new cdk.CfnOutput(this, 'ApiGatewayCertificateArn', {
value: this.apiGatewayCertificate.certificateArn,
exportName: this.exportName('ApiGatewayCertificateArn'),
description: 'ACM Wildcard Certificate ARN for API Gateway (us-east-1)',
});
// Export API Gateway certificate domain for verification
new cdk.CfnOutput(this, 'ApiGatewayCertificateDomain', {
value: '*.everyonecook.cloud',
description: 'Domain name for API Gateway certificate (wildcard)',
});
}
}
Critical: This stack MUST be deployed to us-east-1:
// In infrastructure/bin/app.ts
const certificateStack = new CertificateStack(app, `${stackPrefix}-Certificate`, {
env: {
account: config.account,
region: 'us-east-1', // MUST be us-east-1 for CloudFront
},
config,
description: `ACM Certificate for CloudFront (${config.environment}) - us-east-1`,
});
Why us-east-1?
The stack creates two certificates with environment-specific domains:
CloudFront Certificate:
// Dev environment
domainName: 'cdn-dev.everyonecook.cloud'
// Staging environment
domainName: 'cdn-staging.everyonecook.cloud'
// Production environment
domainName: 'cdn.everyonecook.cloud'
API Gateway Wildcard Certificate:
domainName: '*.everyonecook.cloud' // Covers all subdomains
subjectAlternativeNames: ['everyonecook.cloud'] // Also covers root domain
// Covers:
// - api.everyonecook.cloud
// - api-dev.everyonecook.cloud
// - api-staging.everyonecook.cloud
// - Any future *.everyonecook.cloud subdomains
ACM automatically creates DNS validation records in Route 53:
validation: acm.CertificateValidation.fromDns(hostedZone)
What Happens:
The certificate created in us-east-1 is used by other stacks in ap-southeast-1:
// In Core Stack (ap-southeast-1) - imports CloudFront certificate from us-east-1
const certificate = acm.Certificate.fromCertificateArn(
this,
'ImportedCloudFrontCertificate',
'arn:aws:acm:us-east-1:616580903213:certificate/8d53776e-0480-47d2-a6ff-4fe9b2eb6534'
);
Decision: CloudFront WAF Web ACL removed to save costs:
// REMOVED: CloudFront WAF Web ACL
// Previous monthly cost: $9/month = $108/year
// Protection Status:
// Shield Standard: DDoS protection (free, auto-enabled)
// CloudFront OAC: Blocks direct S3 access
// Signed URLs: Private content protection
// ❌ WAF: Removed (cost optimization)
Rationale:
After deployment, the stack exports the following values:
| Output Name | Value | Usage |
|---|---|---|
CloudFrontCertificateArn | arn:aws:acm:us-east-1:616580903213:certificate/8d53776e-... | Used by Core Stack for CloudFront distribution |
CloudFrontCertificateDomain | cdn.everyonecook.cloud (or cdn-dev) | Verification only |
ApiGatewayCertificateArn | arn:aws:acm:us-east-1:616580903213:certificate/a1b2c3d4-... | Used by Backend Stack for API Gateway domain |
ApiGatewayCertificateDomain | *.everyonecook.cloud | Verification only (wildcard) |
Before deploying Certificate Stack, ensure:
Verify DNS is working:
nslookup -type=NS everyonecook.cloud
Navigate to infrastructure directory:
cd D:\Project_AWS\everyonecook\infrastructure
Deploy Certificate Stack to us-east-1:
# Deploy Certificate Stack
npx cdk deploy EveryoneCook-dev-Certificate --context environment=dev
Important: Notice the region is us-east-1, not ap-southeast-1.
Expected output:
✨ Synthesis time: 6.12s
EveryoneCook-dev-Certificate: deploying...
[████████████████████████████████████████] (3/3)
EveryoneCook-dev-Certificate: creating CloudFormation changeset...
EveryoneCook-dev-Certificate
✨ Deployment time: 125.34s
Outputs:
EveryoneCook-dev-Certificate.CloudFrontCertificateArn =
arn:aws:acm:us-east-1:616580903213:certificate/8d53776e-0480-47d2-a6ff-4fe9b2eb6534
EveryoneCook-dev-Certificate.CloudFrontCertificateDomain = cdn-dev.everyonecook.cloud
EveryoneCook-dev-Certificate.ApiGatewayCertificateArn =
arn:aws:acm:us-east-1:616580903213:certificate/a1b2c3d4-5678-90ef-ghij-klmnopqrstuv
EveryoneCook-dev-Certificate.ApiGatewayCertificateDomain = *.everyonecook.cloud
Stack ARN:
arn:aws:cloudformation:us-east-1:616580903213:stack/EveryoneCook-dev-Certificate/...
ACM certificates require DNS validation. This process takes 5-10 minutes:
You can monitor the validation progress in AWS Console.
Switch AWS Console to us-east-1 region before viewing certificates
cdn-dev.everyonecook.cloud
ACM Certificate for CloudFront showing “Issued” status, domain name, validation method (DNS), and CNAME validation record
*.everyonecook.cloud
ACM Wildcard Certificate for API Gateway showing domain *.everyonecook.cloud, SAN everyonecook.cloud, and validation records
everyonecook.cloud hosted zone
Route 53 showing CNAME validation records automatically created by ACM for certificate validation
Expected records:
_abc123def456.cdn-dev.everyonecook.cloud CNAME _xyz789.acm-validations.aws.
_ghi789jkl012.everyonecook.cloud CNAME _mno345.acm-validations.aws.
| Resource | Cost | Notes |
|---|---|---|
| ACM Certificates | $0/month | Free for certificates used with AWS services |
| DNS Validation Records | $0/month | Included in Route 53 hosted zone cost |
| Certificate Renewal | $0/month | Automatic renewal (free) |
| Total | $0/month | 100% free (no ongoing costs) |
Annual Savings from WAF Removal: $108/year
The Certificate Stack imports from DNS Stack:
// Hardcoded Hosted Zone ID (stable, doesn't change)
const hostedZoneId = 'Z018823421GWCSYG5UMHV';
const hostedZone = route53.HostedZone.fromHostedZoneAttributes(this, 'HostedZone', {
hostedZoneId: hostedZoneId,
zoneName: 'everyonecook.cloud',
});
Why Hardcoded?
Fn.importValue for cross-region references (us-east-1 ← ap-southeast-1)The Certificate Stack exports are used by:
Core Stack (Phase 2)
CloudFrontCertificateArnBackend Stack (Phase 4)
ApiGatewayCertificateArnDNS Stack (ap-southeast-1)
│
├─ Hosted Zone ID: Z018823421GWCSYG5UMHV
│
▼
Certificate Stack (us-east-1) ← MUST be us-east-1
│
├─ CloudFront Certificate ARN
│ └─► Core Stack (ap-southeast-1)
│
└─ API Gateway Certificate ARN
└─► Backend Stack (ap-southeast-1)
Before proceeding to Core Stack deployment:
After successfully deploying the Certificate Stack:
➡️ 5.4.3 Core Stack - Create DynamoDB, S3, and CloudFront infrastructure
The Core Stack will:
CloudFrontCertificateArn from this stackcdn.everyonecook.cloudinfrastructure/lib/stacks/certificate-stack.tsinfrastructure/lib/base-stack.tsinfrastructure/bin/app.ts