Skip to content

Commit c0bddd4

Browse files
committed
Implement HTTPS
1 parent 3a88eab commit c0bddd4

File tree

1 file changed

+117
-59
lines changed

1 file changed

+117
-59
lines changed

backend/src/iac/backend-stack.ts

Lines changed: 117 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,17 @@ import * as logs from 'aws-cdk-lib/aws-logs';
55
import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2';
66
import * as cognito from 'aws-cdk-lib/aws-cognito';
77
import * as elbv2_actions from 'aws-cdk-lib/aws-elasticloadbalancingv2-actions';
8+
import * as acm from 'aws-cdk-lib/aws-certificatemanager';
9+
import * as route53 from 'aws-cdk-lib/aws-route53';
10+
import * as targets from 'aws-cdk-lib/aws-route53-targets';
811
import { Construct } from 'constructs';
912
import { AttributeType, BillingMode, Table } from 'aws-cdk-lib/aws-dynamodb';
1013
import { RemovalPolicy } from 'aws-cdk-lib';
1114

1215
interface BackendStackProps extends cdk.StackProps {
1316
environment: string;
17+
domainName?: string; // Optional domain name for certificate
18+
hostedZoneId?: string; // Optional hosted zone ID for domain
1419
}
1520

1621
export class BackendStack extends cdk.Stack {
@@ -21,23 +26,14 @@ export class BackendStack extends cdk.Stack {
2126
const appName = 'AIMedicalReport';
2227

2328
// Look up existing VPC or create a new one
24-
let vpc: ec2.IVpc;
25-
try {
26-
vpc = ec2.Vpc.fromLookup(this, `${appName}VPC`, {
27-
isDefault: false,
28-
vpcName: `${appName}VPC`,
29-
});
30-
} catch {
31-
vpc = new ec2.Vpc(this, `${appName}VPC`, {
32-
vpcName: `${appName}VPC`,
33-
maxAzs: isProd ? 3 : 2,
34-
});
35-
}
29+
const vpc: ec2.IVpc = new ec2.Vpc(this, `${appName}VPC`, {
30+
vpcName: `${appName}VPC-${props.environment}`,
31+
maxAzs: 2,
32+
});
3633

37-
// Look up existing ECS Cluster or create a new one
3834
const cluster = new ecs.Cluster(this, `${appName}Cluster`, {
3935
vpc,
40-
clusterName: `${appName}Cluster`,
36+
clusterName: `${appName}Cluster-${props.environment}`,
4137
containerInsights: true,
4238
});
4339

@@ -49,13 +45,17 @@ export class BackendStack extends cdk.Stack {
4945
});
5046

5147
// Task Definition
52-
const taskDefinition = new ecs.FargateTaskDefinition(this, `${appName}TaskDef`, {
53-
memoryLimitMiB: isProd ? 1024 : 512,
54-
cpu: isProd ? 512 : 256,
55-
});
48+
const taskDefinition = new ecs.FargateTaskDefinition(
49+
this,
50+
`${appName}TaskDef-${props.environment}`,
51+
{
52+
memoryLimitMiB: isProd ? 1024 : 512,
53+
cpu: isProd ? 512 : 256,
54+
},
55+
);
5656

5757
// Container
58-
const container = taskDefinition.addContainer(`${appName}Container`, {
58+
const container = taskDefinition.addContainer(`${appName}Container-${props.environment}`, {
5959
image: ecs.ContainerImage.fromAsset('../backend/', {
6060
file: 'Dockerfile.prod',
6161
buildArgs: {
@@ -83,48 +83,87 @@ export class BackendStack extends cdk.Stack {
8383
const userPool = cognito.UserPool.fromUserPoolId(
8484
this,
8585
`${appName}UserPool`,
86-
'ai-cognito-medical-reports-user-pool',
86+
'us-east-1_PszlvSmWc',
8787
);
8888

8989
// Create a Cognito domain if it doesn't exist
90-
const userPoolDomain = new cognito.UserPoolDomain(this, `${appName}UserPoolDomain`, {
91-
userPool,
92-
cognitoDomain: {
93-
domainPrefix: `${appName.toLowerCase()}-auth`,
94-
},
95-
});
96-
97-
// Create a Cognito User Pool Client for the ALB
98-
const userPoolClient = new cognito.UserPoolClient(this, `${appName}UserPoolClient`, {
99-
userPool,
100-
generateSecret: true,
101-
authFlows: {
102-
userPassword: true,
103-
userSrp: true,
104-
},
105-
oAuth: {
106-
flows: {
107-
authorizationCodeGrant: true,
90+
const userPoolDomain = new cognito.UserPoolDomain(
91+
this,
92+
`${appName}UserPoolDomain-${props.environment}`,
93+
{
94+
userPool,
95+
cognitoDomain: {
96+
domainPrefix: `${appName.toLowerCase()}-auth-${props.environment}-modus`,
10897
},
109-
callbackUrls: [`http://${appName.toLowerCase()}.example.com/oauth2/idpresponse`], // Update with your actual domain
11098
},
111-
});
99+
);
112100

113101
// Create ALB
114-
const alb = new elbv2.ApplicationLoadBalancer(this, `${appName}ALB`, {
102+
const alb = new elbv2.ApplicationLoadBalancer(this, `${appName}ALB-${props.environment}`, {
115103
vpc,
116104
internetFacing: true,
117105
loadBalancerName: `${appName}-${props.environment}`,
118106
});
119107

108+
// HTTPS IMPLEMENTATION - CERTIFICATE
109+
let certificate;
110+
if (props.domainName && props.hostedZoneId) {
111+
// If domain name is provided, create or import certificate
112+
const hostedZone = route53.HostedZone.fromHostedZoneAttributes(this, 'HostedZone', {
113+
hostedZoneId: props.hostedZoneId,
114+
zoneName: props.domainName,
115+
});
116+
117+
certificate = new acm.Certificate(this, `${appName}Certificate-${props.environment}`, {
118+
domainName: props.domainName,
119+
validation: acm.CertificateValidation.fromDns(hostedZone),
120+
});
121+
122+
// Create DNS record for ALB
123+
new route53.ARecord(this, `${appName}AliasRecord-${props.environment}`, {
124+
zone: hostedZone,
125+
recordName: props.domainName,
126+
target: route53.RecordTarget.fromAlias(new targets.LoadBalancerTarget(alb)),
127+
});
128+
} else {
129+
// For development or when no domain is provided, generate a self-signed certificate
130+
certificate = new acm.Certificate(this, `${appName}SelfSignedCert-${props.environment}`, {
131+
domainName: alb.loadBalancerDnsName,
132+
validation: acm.CertificateValidation.fromDns(),
133+
});
134+
}
135+
136+
// Create a Cognito User Pool Client for the ALB
137+
const userPoolClient = new cognito.UserPoolClient(
138+
this,
139+
`${appName}UserPoolClient-${props.environment}`,
140+
{
141+
userPool,
142+
generateSecret: true,
143+
authFlows: {
144+
userPassword: true,
145+
userSrp: true,
146+
},
147+
oAuth: {
148+
flows: {
149+
authorizationCodeGrant: true,
150+
},
151+
// Update callback URLs to use HTTPS
152+
callbackUrls: props.domainName
153+
? [`https://${props.domainName}/oauth2/idpresponse`]
154+
: [`https://${alb.loadBalancerDnsName}/oauth2/idpresponse`],
155+
},
156+
},
157+
);
158+
120159
// Create Fargate Service
121-
const fargateService = new ecs.FargateService(this, `${appName}Service`, {
160+
const fargateService = new ecs.FargateService(this, `${appName}Service-${props.environment}`, {
122161
cluster,
123162
taskDefinition,
124163
desiredCount: isProd ? 2 : 1,
125164
assignPublicIp: false,
126165
securityGroups: [
127-
new ec2.SecurityGroup(this, `${appName}ServiceSG`, {
166+
new ec2.SecurityGroup(this, `${appName}ServiceSG-${props.environment}`, {
128167
vpc,
129168
allowAllOutbound: true,
130169
}),
@@ -146,23 +185,31 @@ export class BackendStack extends cdk.Stack {
146185
}
147186

148187
// Create ALB Target Group
149-
const targetGroup = new elbv2.ApplicationTargetGroup(this, `${appName}TargetGroup`, {
150-
vpc,
151-
port: 3000,
152-
protocol: elbv2.ApplicationProtocol.HTTP,
153-
targetType: elbv2.TargetType.IP,
154-
healthCheck: {
155-
path: '/health',
156-
interval: cdk.Duration.seconds(30),
157-
timeout: cdk.Duration.seconds(5),
188+
const targetGroup = new elbv2.ApplicationTargetGroup(
189+
this,
190+
`${appName}TargetGroup-${props.environment}`,
191+
{
192+
vpc,
193+
port: 3000,
194+
protocol: elbv2.ApplicationProtocol.HTTP,
195+
targetType: elbv2.TargetType.IP,
196+
healthCheck: {
197+
path: '/health',
198+
interval: cdk.Duration.seconds(30),
199+
timeout: cdk.Duration.seconds(5),
200+
},
201+
targets: [fargateService],
158202
},
159-
targets: [fargateService],
160-
});
203+
);
161204

162-
// Create HTTP Listener
163-
alb.addListener(`${appName}HttpListener`, {
164-
port: 80,
165-
protocol: elbv2.ApplicationProtocol.HTTP,
205+
// HTTPS IMPLEMENTATION - LISTENERS
206+
207+
// Create HTTPS Listener
208+
alb.addListener(`${appName}HttpsListener-${props.environment}`, {
209+
port: 443,
210+
protocol: elbv2.ApplicationProtocol.HTTPS,
211+
certificates: [certificate],
212+
sslPolicy: elbv2.SslPolicy.RECOMMENDED,
166213
defaultAction: new elbv2_actions.AuthenticateCognitoAction({
167214
userPool,
168215
userPoolClient,
@@ -172,8 +219,19 @@ export class BackendStack extends cdk.Stack {
172219
}),
173220
});
174221

222+
// Create HTTP Listener that redirects to HTTPS
223+
alb.addListener(`${appName}HttpListener-${props.environment}`, {
224+
port: 80,
225+
protocol: elbv2.ApplicationProtocol.HTTP,
226+
defaultAction: elbv2.ListenerAction.redirect({
227+
protocol: elbv2.ApplicationProtocol.HTTPS,
228+
port: '443',
229+
permanent: true,
230+
}),
231+
});
232+
175233
// Create DynamoDB table for reports
176-
const reportsTable = new Table(this, `${appName}ReportsTable`, {
234+
const reportsTable = new Table(this, `${appName}ReportsTable-${props.environment}`, {
177235
tableName: `${appName}ReportsTable${props.environment}`,
178236
partitionKey: {
179237
name: 'userId',

0 commit comments

Comments
 (0)