@@ -125,19 +125,55 @@ export class BackendStack extends cdk.Stack {
125125 'Allow inbound HTTPS traffic from within VPC' ,
126126 ) ;
127127
128- // Task Definition
128+ // Create Task Execution Role - this is used during task startup
129+ const taskExecutionRole = new iam . Role (
130+ this ,
131+ `${ appName } TaskExecutionRole-${ props . environment } ` ,
132+ {
133+ assumedBy : new iam . ServicePrincipal ( 'ecs-tasks.amazonaws.com' ) ,
134+ description :
135+ 'Role that the ECS service uses to pull container images and publish logs to CloudWatch' ,
136+ managedPolicies : [
137+ iam . ManagedPolicy . fromAwsManagedPolicyName (
138+ 'service-role/AmazonECSTaskExecutionRolePolicy' ,
139+ ) ,
140+ ] ,
141+ } ,
142+ ) ;
143+
144+ // Create Task Role - this is used by the container during runtime
145+ const taskRole = new iam . Role ( this , `${ appName } TaskRole-${ props . environment } ` , {
146+ assumedBy : new iam . ServicePrincipal ( 'ecs-tasks.amazonaws.com' ) ,
147+ description : 'Role that the containers in the task assume' ,
148+ } ) ;
149+
150+ // Grant permissions to the task role
151+ // DynamoDB permissions
152+ reportsTable . grantReadWriteData ( taskRole ) ;
153+
154+ // Add permission to read Perplexity API key from Secrets Manager
155+ taskRole . addToPolicy (
156+ new iam . PolicyStatement ( {
157+ effect : iam . Effect . ALLOW ,
158+ actions : [ 'secretsmanager:GetSecretValue' , 'secretsmanager:DescribeSecret' ] ,
159+ resources : [
160+ `arn:aws:secretsmanager:${ this . region } :${ this . account } :secret:medical-reports-explainer/${ props . environment } /perplexity-api-key-*` ,
161+ ] ,
162+ } ) ,
163+ ) ;
164+
165+ // Task Definition with explicit roles
129166 const taskDefinition = new ecs . FargateTaskDefinition (
130167 this ,
131168 `${ appName } TaskDef-${ props . environment } ` ,
132169 {
133170 memoryLimitMiB : isProd ? 1024 : 512 ,
134171 cpu : isProd ? 512 : 256 ,
172+ taskRole : taskRole , // Role that the application uses to call AWS services
173+ executionRole : taskExecutionRole , // Role that ECS uses to pull images and write logs
135174 } ,
136175 ) ;
137176
138- // Grant DynamoDB permissions to task
139- reportsTable . grantReadWriteData ( taskDefinition . taskRole ) ;
140-
141177 // Create a secrets manager for the SSL certificate and key
142178 const certificateSecret = new cdk . aws_secretsmanager . Secret (
143179 this ,
@@ -194,7 +230,7 @@ export class BackendStack extends cdk.Stack {
194230 } ) ;
195231
196232 // Grant the task role access to read the SSL certificate secret
197- certificateSecret . grantRead ( taskDefinition . taskRole ) ;
233+ certificateSecret . grantRead ( taskRole ) ;
198234
199235 container . addPortMappings ( {
200236 containerPort : 3000 ,
@@ -307,6 +343,9 @@ export class BackendStack extends cdk.Stack {
307343 // Create the 'api' resource
308344 const apiResource = api . root . addResource ( 'api' ) ;
309345
346+ // Create the 'docs' resource under 'api'
347+ const docsResource = apiResource . addResource ( 'docs' ) ;
348+
310349 // Create the 'reports' resource under 'api'
311350 const reportsResource = apiResource . addResource ( 'reports' ) ;
312351
@@ -325,6 +364,13 @@ export class BackendStack extends cdk.Stack {
325364 vpcLink : vpcLink ,
326365 } ;
327366
367+ const getDocsIntegration = new apigateway . Integration ( {
368+ type : apigateway . IntegrationType . HTTP_PROXY ,
369+ integrationHttpMethod : 'GET' ,
370+ uri : `${ serviceUrl } /api/docs` ,
371+ options : integrationOptions ,
372+ } ) ;
373+
328374 // Create integrations for each endpoint
329375 const getReportsIntegration = new apigateway . Integration ( {
330376 type : apigateway . IntegrationType . HTTP_PROXY ,
@@ -373,7 +419,7 @@ export class BackendStack extends cdk.Stack {
373419 // Add methods to the resources
374420 reportsResource . addMethod ( 'GET' , getReportsIntegration , methodOptions ) ;
375421 latestResource . addMethod ( 'GET' , getLatestReportIntegration , methodOptions ) ;
376-
422+ docsResource . addMethod ( 'GET' , getDocsIntegration , methodOptions ) ;
377423 // For path parameter methods, add the request parameter configuration
378424 reportIdResource . addMethod ( 'GET' , getReportByIdIntegration , {
379425 ...methodOptions ,
@@ -404,31 +450,7 @@ export class BackendStack extends cdk.Stack {
404450 latestResource . addCorsPreflight ( corsOptions ) ;
405451 reportIdResource . addCorsPreflight ( corsOptions ) ;
406452 reportStatusResource . addCorsPreflight ( corsOptions ) ;
407-
408- // Apply resource policy separately after resources and methods are created
409- // const apiResourcePolicy = new iam.PolicyDocument({
410- // statements: [
411- // // Allow authenticated Cognito users
412- // new iam.PolicyStatement({
413- // effect: iam.Effect.ALLOW,
414- // principals: [new iam.AnyPrincipal()],
415- // actions: ['execute-api:Invoke'],
416- // resources: [`arn:aws:execute-api:${this.region}:${this.account}:${api.restApiId}/*/*`],
417- // }),
418- // // Deny non-HTTPS requests
419- // new iam.PolicyStatement({
420- // effect: iam.Effect.DENY,
421- // principals: [new iam.AnyPrincipal()],
422- // actions: ['execute-api:Invoke'],
423- // resources: [`arn:aws:execute-api:${this.region}:${this.account}:${api.restApiId}/*/*`],
424- // conditions: {
425- // Bool: {
426- // 'aws:SecureTransport': 'false',
427- // },
428- // },
429- // }),
430- // ],
431- // });
453+ docsResource . addCorsPreflight ( corsOptions ) ;
432454
433455 // Create API Gateway execution role with required permissions
434456 new iam . Role ( this , `${ appName } APIGatewayRole-${ props . environment } ` , {
0 commit comments