@@ -10,10 +10,9 @@ import {
1010 Bucket ,
1111 BucketEncryption ,
1212 BlockPublicAccess ,
13- CfnBucket ,
14- CfnBucketPolicy
13+ ObjectOwnership
1514} from "aws-cdk-lib/aws-s3"
16- import { CfnFunction } from "aws-cdk-lib/aws-lambda "
15+ import * as AWSCDK from "aws-cdk-lib/aws-s3 "
1716import { Key } from "aws-cdk-lib/aws-kms"
1817import { PolicyStatement } from "aws-cdk-lib/aws-iam"
1918import {
@@ -59,13 +58,14 @@ export class EpsAssistMeStack extends Stack {
5958 encryption : BucketEncryption . KMS ,
6059 encryptionKey : cloudWatchLogsKmsKey ,
6160 removalPolicy : RemovalPolicy . DESTROY ,
62- autoDeleteObjects : true ,
6361 blockPublicAccess : BlockPublicAccess . BLOCK_ALL ,
64- versioned : true
62+ versioned : true ,
63+ objectLockEnabled : true ,
64+ objectOwnership : ObjectOwnership . BUCKET_OWNER_ENFORCED
6565 } )
6666
67- // Add S3 replication and logging
68- const accessLogBucketCfn = accessLogBucket . node . defaultChild as CfnBucket
67+ // Replication config via escape hatch
68+ const accessLogBucketCfn = accessLogBucket . node . defaultChild as AWSCDK . CfnBucket
6969 accessLogBucketCfn . replicationConfiguration = {
7070 role : `arn:aws:iam::${ account } :role/account-resources-s3-replication-role` ,
7171 rules : [ {
@@ -77,24 +77,25 @@ export class EpsAssistMeStack extends Stack {
7777 deleteMarkerReplication : { status : "Disabled" }
7878 } ]
7979 }
80- accessLogBucketCfn . loggingConfiguration = {
81- destinationBucketName : accessLogBucket . bucketName ,
82- logFilePrefix : "self-logs/"
83- }
8480
85- new CfnBucketPolicy ( this , "AccessLogsBucketStrictTLSOnly" , {
86- bucket : accessLogBucket . bucketName ,
81+ // TLS-only policy (strictly compliant for cfn-guard)
82+ new AWSCDK . CfnBucketPolicy ( this , "AccessLogsBucketTlsPolicy" , {
83+ bucket : accessLogBucketCfn . ref ,
8784 policyDocument : {
8885 Version : "2012-10-17" ,
89- Statement : [ {
90- Action : "s3:*" ,
91- Effect : "Deny" ,
92- Principal : "*" ,
93- Resource : "*" ,
94- Condition : {
95- Bool : { "aws:SecureTransport" : false }
86+ Statement : [
87+ {
88+ Action : "s3:*" ,
89+ Effect : "Deny" ,
90+ Principal : "*" ,
91+ Resource : "*" ,
92+ Condition : {
93+ Bool : {
94+ "aws:SecureTransport" : false
95+ }
96+ }
9697 }
97- } ]
98+ ]
9899 }
99100 } )
100101
@@ -103,14 +104,16 @@ export class EpsAssistMeStack extends Stack {
103104 encryptionKey : cloudWatchLogsKmsKey ,
104105 encryption : BucketEncryption . KMS ,
105106 removalPolicy : RemovalPolicy . DESTROY ,
106- autoDeleteObjects : true ,
107107 blockPublicAccess : BlockPublicAccess . BLOCK_ALL ,
108108 versioned : true ,
109+ objectLockEnabled : true ,
110+ objectOwnership : ObjectOwnership . BUCKET_OWNER_ENFORCED ,
109111 serverAccessLogsBucket : accessLogBucket ,
110112 serverAccessLogsPrefix : "s3-access-logs/"
111113 } )
112114
113- const kbDocsBucketCfn = kbDocsBucket . node . defaultChild as CfnBucket
115+ // Replication config via escape hatch
116+ const kbDocsBucketCfn = kbDocsBucket . node . defaultChild as AWSCDK . CfnBucket
114117 kbDocsBucketCfn . replicationConfiguration = {
115118 role : `arn:aws:iam::${ account } :role/account-resources-s3-replication-role` ,
116119 rules : [ {
@@ -123,19 +126,24 @@ export class EpsAssistMeStack extends Stack {
123126 } ]
124127 }
125128
126- new CfnBucketPolicy ( this , "KbDocsBucketStrictTLSOnly" , {
127- bucket : kbDocsBucket . bucketName ,
129+ // TLS-only policy (strictly compliant for cfn-guard)
130+ new AWSCDK . CfnBucketPolicy ( this , "KbDocsTlsPolicy" , {
131+ bucket : kbDocsBucketCfn . ref ,
128132 policyDocument : {
129133 Version : "2012-10-17" ,
130- Statement : [ {
131- Action : "s3:*" ,
132- Effect : "Deny" ,
133- Principal : "*" ,
134- Resource : "*" ,
135- Condition : {
136- Bool : { "aws:SecureTransport" : false }
134+ Statement : [
135+ {
136+ Action : "s3:*" ,
137+ Effect : "Deny" ,
138+ Principal : "*" ,
139+ Resource : "*" ,
140+ Condition : {
141+ Bool : {
142+ "aws:SecureTransport" : false
143+ }
144+ }
137145 }
138- } ]
146+ ]
139147 }
140148 } )
141149
@@ -175,6 +183,7 @@ export class EpsAssistMeStack extends Stack {
175183 type : "VECTORSEARCH"
176184 } )
177185
186+ // OpenSearch encryption policy (AWS-owned key)
178187 new ops . CfnSecurityPolicy ( this , "OsEncryptionPolicy" , {
179188 name : "eps-assist-encryption-policy" ,
180189 type : "encryption" ,
@@ -184,16 +193,19 @@ export class EpsAssistMeStack extends Stack {
184193 } )
185194 } )
186195
196+ // OpenSearch network policy (allow public access for demo purposes)
187197 new ops . CfnSecurityPolicy ( this , "OsNetworkPolicy" , {
188198 name : "eps-assist-network-policy" ,
189199 type : "network" ,
190- policy : JSON . stringify ( [ {
191- Rules : [
192- { ResourceType : "collection" , Resource : [ "collection/eps-assist-vector-db" ] } ,
193- { ResourceType : "dashboard" , Resource : [ "collection/eps-assist-vector-db" ] }
194- ] ,
195- AllowFromPublic : true
196- } ] )
200+ policy : JSON . stringify ( [
201+ {
202+ Rules : [
203+ { ResourceType : "collection" , Resource : [ "collection/eps-assist-vector-db" ] } ,
204+ { ResourceType : "dashboard" , Resource : [ "collection/eps-assist-vector-db" ] }
205+ ] ,
206+ AllowFromPublic : true
207+ }
208+ ] )
197209 } )
198210
199211 // ==== Lambda Function: CreateIndex ====
@@ -208,32 +220,22 @@ export class EpsAssistMeStack extends Stack {
208220 additionalPolicies : [ ]
209221 } )
210222
211- // Add cfn-guard suppressions for CreateIndex Lambda
212- const createIndexLambdaCfn = createIndexFunction . function . node . defaultChild as CfnFunction
213- createIndexLambdaCfn . cfnOptions . metadata = {
214- guard : {
215- SuppressedRules : [
216- "LAMBDA_DLQ_CHECK" ,
217- "LAMBDA_INSIDE_VPC" ,
218- "LAMBDA_CONCURRENCY_CHECK"
219- ]
220- }
221- }
222-
223- // ==== OpenSearch Access Policy ====
223+ // Access policy for Bedrock + Lambda to use the collection and index
224224 new ops . CfnAccessPolicy ( this , "OsAccessPolicy" , {
225225 name : "eps-assist-access-policy" ,
226226 type : "data" ,
227- policy : JSON . stringify ( [ {
228- Rules : [
229- { ResourceType : "collection" , Resource : [ "collection/*" ] , Permission : [ "aoss:*" ] } ,
230- { ResourceType : "index" , Resource : [ "index/*/*" ] , Permission : [ "aoss:*" ] }
231- ] ,
232- Principal : [
233- `arn:aws:iam::${ account } :role/${ createIndexFunction . function . role ?. roleName } ` ,
234- `arn:aws:iam::${ account } :root`
235- ]
236- } ] )
227+ policy : JSON . stringify ( [
228+ {
229+ Rules : [
230+ { ResourceType : "collection" , Resource : [ "collection/*" ] , Permission : [ "aoss:*" ] } ,
231+ { ResourceType : "index" , Resource : [ "index/*/*" ] , Permission : [ "aoss:*" ] }
232+ ] ,
233+ Principal : [
234+ `arn:aws:iam::${ account } :role/${ createIndexFunction . function . role ?. roleName } ` ,
235+ `arn:aws:iam::${ account } :root`
236+ ]
237+ }
238+ ] )
237239 } )
238240
239241 // ==== Trigger Vector Index Creation ====
@@ -302,6 +304,7 @@ export class EpsAssistMeStack extends Stack {
302304 }
303305 } )
304306
307+ // Attach S3 data source to Knowledge Base
305308 new CfnDataSource ( this , "EpsKbDataSource" , {
306309 name : "eps-assist-kb-ds" ,
307310 knowledgeBaseId : kb . attrKnowledgeBaseId ,
@@ -330,6 +333,7 @@ export class EpsAssistMeStack extends Stack {
330333 SLACK_SIGNING_SECRET : slackSigningSecret
331334 }
332335
336+ // SlackBot Lambda function
333337 const slackBotLambda = new LambdaFunction ( this , "SlackBotLambda" , {
334338 stackName : props . stackName ,
335339 functionName : `${ props . stackName } -SlackBotFunction` ,
@@ -341,34 +345,6 @@ export class EpsAssistMeStack extends Stack {
341345 additionalPolicies : [ ]
342346 } )
343347
344- const slackBotLambdaCfn = slackBotLambda . function . node . defaultChild as CfnFunction
345- slackBotLambdaCfn . cfnOptions . metadata = {
346- guard : {
347- SuppressedRules : [
348- "LAMBDA_DLQ_CHECK" ,
349- "LAMBDA_INSIDE_VPC" ,
350- "LAMBDA_CONCURRENCY_CHECK"
351- ]
352- }
353- }
354-
355- // AwsCustomResource internal Lambda handler suppression
356- const customResourceHandler = this . node
357- . tryFindChild ( "VectorIndex" )
358- ?. node . tryFindChild ( "CustomResourceProvider" )
359- ?. node . tryFindChild ( "Handler" )
360- const customResourceHandlerCfn = customResourceHandler ?. node . defaultChild as CfnFunction
361- if ( customResourceHandlerCfn ) {
362- customResourceHandlerCfn . cfnOptions . metadata = {
363- guard : {
364- SuppressedRules : [
365- "LAMBDA_DLQ_CHECK" ,
366- "LAMBDA_INSIDE_VPC"
367- ]
368- }
369- }
370- }
371-
372348 // ==== API Gateway + Slack Route ====
373349 const apiGateway = new RestApiGateway ( this , "EpsAssistApiGateway" , {
374350 stackName : props . stackName ,
@@ -378,54 +354,19 @@ export class EpsAssistMeStack extends Stack {
378354 truststoreVersion : "unused"
379355 } )
380356
357+ // API Route
381358 const slackRoute = apiGateway . api . root . addResource ( "slack" ) . addResource ( "ask-eps" )
382359 slackRoute . addMethod ( "POST" , new LambdaIntegration ( slackBotLambda . function , {
383360 credentialsRole : apiGateway . role
384361 } ) )
385362
386363 apiGateway . role . addManagedPolicy ( slackBotLambda . executionPolicy )
387364
365+ // Output the SlackBot API endpoint
388366 new CfnOutput ( this , "SlackBotEndpoint" , {
389367 value : `https://${ apiGateway . api . domainName ?. domainName } /slack/ask-eps`
390368 } )
391369
392- // ==== Suppressions for CDK-generated Lambda handlers ====
393- // Suppress CDK-generated S3 AutoDeleteObjects handler
394- const customS3Handler = Stack . of ( this ) . node
395- . tryFindChild ( "Custom::S3AutoDeleteObjectsCustomResourceProvider" )
396- ?. node . tryFindChild ( "Handler" )
397-
398- const customS3HandlerCfn = customS3Handler ?. node . defaultChild as CfnFunction
399-
400- if ( customS3HandlerCfn ) {
401- customS3HandlerCfn . cfnOptions . metadata = {
402- guard : {
403- SuppressedRules : [
404- "LAMBDA_DLQ_CHECK" ,
405- "LAMBDA_INSIDE_VPC"
406- ]
407- }
408- }
409- }
410-
411- // Suppress AWS679f... CDK-generated unnamed Lambda (e.g., AwsCustomResource)
412- for ( const construct of this . node . findAll ( ) ) {
413- const fn = construct . node . defaultChild
414- if (
415- fn instanceof CfnFunction &&
416- fn . logicalId . startsWith ( "AWS679f" )
417- ) {
418- fn . cfnOptions . metadata = {
419- guard : {
420- SuppressedRules : [
421- "LAMBDA_DLQ_CHECK" ,
422- "LAMBDA_INSIDE_VPC"
423- ]
424- }
425- }
426- }
427- }
428-
429370 // ==== Final CDK Nag Suppressions ====
430371 nagSuppressions ( this )
431372 }
0 commit comments