Skip to content

Commit 202be66

Browse files
author
Jon Slominski
committed
feat(cdk): add multi-region certificate support for CloudFront and ALB
- Split single certificateArn into cdnCertificateArn and albCertificateArn - Add validation for certificate regions (CDN: us-east-1, ALB: deployment region) - Update deployment examples with multi-region certificate configurations - Support mixed HTTPS/HTTP configurations (CloudFront HTTPS, ALB HTTP) - Maintain backward compatibility with single certificate setups
1 parent 48b0bc5 commit 202be66

File tree

2 files changed

+68
-22
lines changed

2 files changed

+68
-22
lines changed

README.md

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,10 +117,22 @@ cdk bootstrap
117117
cdk deploy --all
118118
```
119119

120-
Or with domain configuration:
120+
Or with domain configuration (single region - us-east-1):
121+
122+
```bash
123+
cdk deploy --all --context cdnCertificateArn=arn:aws:acm:us-east-1:123456789012:certificate/abc123 --context albCertificateArn=arn:aws:acm:us-east-1:123456789012:certificate/abc123 --context customDomain=mcp-server.example.com
124+
```
125+
126+
Or with multi-region certificate configuration:
121127

122128
```bash
123-
cdk deploy --all --context certificateArn=arn:aws:acm:... --context customDomain=mcp-server.example.com
129+
cdk deploy --all --context cdnCertificateArn=arn:aws:acm:us-east-1:123456789012:certificate/abc123 --context albCertificateArn=arn:aws:acm:eu-west-1:123456789012:certificate/def456 --context customDomain=mcp-server.example.com
130+
```
131+
132+
Or with CloudFront HTTPS only (ALB stays HTTP):
133+
134+
```bash
135+
cdk deploy --all --context cdnCertificateArn=arn:aws:acm:us-east-1:123456789012:certificate/abc123 --context customDomain=mcp-server.example.com
124136
```
125137

126138
5. Update MCP servers:
@@ -134,7 +146,7 @@ cdk bootstrap
134146
Or with domain configuration:
135147

136148
```bash
137-
cdk deploy MCP-Server --context certificateArn=arn:aws:acm:... --context customDomain=mcp-server.example.com
149+
cdk deploy MCP-Server --context cdnCertificateArn=arn:aws:acm:us-east-1:123456789012:certificate/abc123 --context albCertificateArn=arn:aws:acm:us-east-1:123456789012:certificate/abc123 --context customDomain=mcp-server.example.com
138150
```
139151

140152
## Deployment Validation
@@ -173,6 +185,8 @@ aws cognito-idp admin-set-user-password --user-pool-id YOUR_USER_POOL_ID --usern
173185

174186
The deployment includes a sample Python MCP client that demonstrates OAuth 2.0 Protected Resource authentication with the deployed servers. This client implements the 2025-06-18 MCP specification with StreamableHTTP transport.
175187

188+
> **Note:** This client is a modified version of the [simple-auth-client example](https://github.com/modelcontextprotocol/python-sdk/tree/main/examples/clients/simple-auth-client) from the official MCP Python SDK.
189+
176190
### Why Use the Python Client?
177191

178192
The included Python client (`source/sample-clients/simple-auth-client-python/`) demonstrates:
@@ -278,7 +292,10 @@ For detailed information, refer to these additional documentation files:
278292

279293
1. **No Dynamic Client Registration (DCR)**: Client credentials must be pre-configured in AWS Cognito
280294
2. **Region availability** depends on AWS Cognito support
281-
3. **Custom domains** require ACM certificates in us-east-1
295+
3. **Multi-region certificate requirements**:
296+
- CloudFront certificates (`cdnCertificateArn`) must be in us-east-1
297+
- ALB certificates (`albCertificateArn`) must be in the deployment region
298+
- Both certificates must cover the same custom domain
282299
4. **CloudFront WAF only**: AWS WAF is configured for CloudFront distribution, not ALB directly
283300
5. **StreamableHTTP transport only**: SSE transport (deprecated) not supported in this implementation
284301
6. **Some MCP clients** may not support remote connections or OAuth flows

source/cdk/ecs-and-lambda/lib/stacks/mcp-server-stack.ts

Lines changed: 47 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -82,18 +82,47 @@ export class MCPServerStack extends cdk.Stack {
8282
},
8383
]);
8484

85-
// Create context parameter for optional certificate ARN and custom domain
86-
const certificateArn = this.node.tryGetContext("certificateArn");
85+
// Create context parameters for multi-region certificate support
86+
const cdnCertificateArn = this.node.tryGetContext("cdnCertificateArn");
87+
const albCertificateArn = this.node.tryGetContext("albCertificateArn");
8788
const customDomain = this.node.tryGetContext("customDomain");
8889

89-
// Validate that if certificate is provided, custom domain must also be provided
90-
if (certificateArn && !customDomain) {
90+
// Validate certificate and domain requirements
91+
if ((cdnCertificateArn || albCertificateArn) && !customDomain) {
9192
throw new Error(
92-
"Custom domain name must be provided when using a certificate. While CloudFront's default domain will support HTTPS, " +
93-
"the Application Load Balancer HTTPS listener and origin configuration require both a valid certificate and matching custom domain name."
93+
"Custom domain name must be provided when using certificates. " +
94+
"CloudFront and ALB require a valid domain name for certificate association."
9495
);
9596
}
9697

98+
// Validate CloudFront certificate is in us-east-1 if provided
99+
if (cdnCertificateArn) {
100+
const cfCertRegion = cdk.Arn.split(
101+
cdnCertificateArn,
102+
cdk.ArnFormat.SLASH_RESOURCE_NAME
103+
).region;
104+
if (cfCertRegion !== "us-east-1") {
105+
throw new Error(
106+
`CloudFront certificate must be in us-east-1 region, but found in ${cfCertRegion}. ` +
107+
"Use cdnCertificateArn context parameter with a certificate from us-east-1."
108+
);
109+
}
110+
}
111+
112+
// Validate ALB certificate is in the current stack region if provided
113+
if (albCertificateArn) {
114+
const albCertRegion = cdk.Arn.split(
115+
albCertificateArn,
116+
cdk.ArnFormat.SLASH_RESOURCE_NAME
117+
).region;
118+
if (albCertRegion !== this.region) {
119+
throw new Error(
120+
`ALB certificate must be in the same region as the stack (${this.region}), but found in ${albCertRegion}. ` +
121+
"Use albCertificateArn context parameter with a certificate from the deployment region."
122+
);
123+
}
124+
}
125+
97126
// Create HTTP and HTTPS security groups for the ALB
98127
const httpSecurityGroup = new ec2.SecurityGroup(
99128
this,
@@ -136,8 +165,8 @@ export class MCPServerStack extends cdk.Stack {
136165
"Allow HTTPS traffic from CloudFront edge locations"
137166
);
138167

139-
// Use the appropriate security group based on certificate presence
140-
this.albSecurityGroup = certificateArn
168+
// Use the appropriate security group based on ALB certificate presence
169+
this.albSecurityGroup = albCertificateArn
141170
? httpsSecurityGroup
142171
: httpSecurityGroup;
143172

@@ -258,16 +287,16 @@ export class MCPServerStack extends cdk.Stack {
258287
true
259288
);
260289

261-
// Create either HTTP or HTTPS listener based on certificate presence
262-
const listener = certificateArn
290+
// Create either HTTP or HTTPS listener based on ALB certificate presence
291+
const listener = albCertificateArn
263292
? this.loadBalancer.addListener("HttpsListener", {
264293
port: 443,
265294
protocol: elbv2.ApplicationProtocol.HTTPS,
266295
certificates: [
267296
acm.Certificate.fromCertificateArn(
268297
this,
269298
"AlbCertificate",
270-
certificateArn
299+
albCertificateArn
271300
),
272301
],
273302
open: false,
@@ -307,7 +336,7 @@ export class MCPServerStack extends cdk.Stack {
307336

308337
// Create CloudFront distribution with protocol matching ALB listener
309338
const albOrigin = new origins.LoadBalancerV2Origin(this.loadBalancer, {
310-
protocolPolicy: certificateArn
339+
protocolPolicy: albCertificateArn
311340
? cloudfront.OriginProtocolPolicy.HTTPS_ONLY
312341
: cloudfront.OriginProtocolPolicy.HTTP_ONLY,
313342
httpPort: 80,
@@ -323,12 +352,12 @@ export class MCPServerStack extends cdk.Stack {
323352
);
324353

325354
// Create the CloudFront distribution with conditional properties
326-
if (customDomain && certificateArn) {
327-
// With custom domain and certificate
355+
if (customDomain && cdnCertificateArn) {
356+
// With custom domain and CDN certificate
328357
const certificate = acm.Certificate.fromCertificateArn(
329358
this,
330359
`MCPServerStackCertificate`,
331-
certificateArn
360+
cdnCertificateArn
332361
);
333362

334363
this.distribution = new cloudfront.Distribution(
@@ -397,8 +426,8 @@ export class MCPServerStack extends cdk.Stack {
397426
},
398427
]);
399428

400-
// Create Route 53 records if custom domain is provided
401-
if (customDomain && certificateArn) {
429+
// Create Route 53 records if custom domain and CDN certificate are provided
430+
if (customDomain && cdnCertificateArn) {
402431
// Look up the hosted zone
403432
const hostedZone = route53.HostedZone.fromLookup(this, "HostedZone", {
404433
domainName: customDomain,
@@ -416,7 +445,7 @@ export class MCPServerStack extends cdk.Stack {
416445

417446
// Set the HTTPS URL
418447
const httpsUrl =
419-
customDomain && certificateArn
448+
customDomain && cdnCertificateArn
420449
? `https://${customDomain}`
421450
: `https://${this.distribution.distributionDomainName}`;
422451

0 commit comments

Comments
 (0)